@lumy-pack/scene-sieve 0.0.8 → 0.0.10
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 +708 -86
- package/dist/index.cjs +382 -4
- package/dist/index.mjs +382 -4
- package/dist/pipeline-worker.mjs +382 -4
- package/package.json +3 -2
- package/dist/cli.d.ts +0 -1
- package/dist/commands/Sieve.d.ts +0 -15
- package/dist/components/PhaseStep.d.ts +0 -14
- package/dist/components/ProgressBar.d.ts +0 -7
- package/dist/constants.d.ts +0 -30
- package/dist/core/analyzer.d.ts +0 -62
- package/dist/core/dbscan.d.ts +0 -10
- package/dist/core/extractor.d.ts +0 -7
- package/dist/core/index.d.ts +0 -8
- package/dist/core/input-resolver.d.ts +0 -13
- package/dist/core/orchestrator.d.ts +0 -2
- package/dist/core/pipeline-worker.d.ts +0 -1
- package/dist/core/pruner.d.ts +0 -61
- package/dist/core/run-in-worker.d.ts +0 -9
- package/dist/core/workspace.d.ts +0 -24
- package/dist/index.d.ts +0 -2
- package/dist/types/index.d.ts +0 -107
- package/dist/utils/logger.d.ts +0 -8
- package/dist/utils/math.d.ts +0 -27
- package/dist/utils/min-heap.d.ts +0 -16
- package/dist/utils/paths.d.ts +0 -18
package/dist/index.cjs
CHANGED
|
@@ -39,7 +39,7 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
39
39
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
40
40
|
|
|
41
41
|
// src/core/orchestrator.ts
|
|
42
|
-
var
|
|
42
|
+
var import_node_crypto2 = require("crypto");
|
|
43
43
|
|
|
44
44
|
// src/utils/logger.ts
|
|
45
45
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
@@ -104,6 +104,8 @@ var PIXELDIFF_GAUSSIAN_KERNEL = 3;
|
|
|
104
104
|
var PIXELDIFF_BINARY_THRESHOLD = 30;
|
|
105
105
|
var PIXELDIFF_CONTOUR_MIN_AREA = 100;
|
|
106
106
|
var PIXELDIFF_SAMPLE_SPACING = 8;
|
|
107
|
+
var DEFAULT_MAX_SEGMENT_DURATION = 300;
|
|
108
|
+
var DEFAULT_SEGMENT_CONCURRENCY = 2;
|
|
107
109
|
function getTempWorkspaceDir(sessionId) {
|
|
108
110
|
return (0, import_node_path.join)(TEMP_BASE_DIR, `${WORKSPACE_PREFIX}${sessionId}`);
|
|
109
111
|
}
|
|
@@ -696,6 +698,23 @@ async function buildFrameList(framesDir, duration) {
|
|
|
696
698
|
extractPath: (0, import_node_path3.join)(framesDir, file)
|
|
697
699
|
}));
|
|
698
700
|
}
|
|
701
|
+
async function extractFramesForRange(inputPath, outputDir, fps, scale, startTime, duration) {
|
|
702
|
+
const outputPattern = (0, import_node_path3.join)(outputDir, FRAME_FILENAME_PATTERN);
|
|
703
|
+
await (0, import_execa.execa)(import_ffmpeg_static.default, [
|
|
704
|
+
"-ss",
|
|
705
|
+
String(startTime),
|
|
706
|
+
"-i",
|
|
707
|
+
inputPath,
|
|
708
|
+
"-t",
|
|
709
|
+
String(duration),
|
|
710
|
+
"-vf",
|
|
711
|
+
`fps=${fps},scale=-1:${scale}`,
|
|
712
|
+
"-q:v",
|
|
713
|
+
"2",
|
|
714
|
+
outputPattern
|
|
715
|
+
]);
|
|
716
|
+
return buildFrameList(outputDir, duration);
|
|
717
|
+
}
|
|
699
718
|
|
|
700
719
|
// src/core/input-resolver.ts
|
|
701
720
|
var import_node_path5 = require("path");
|
|
@@ -758,6 +777,15 @@ async function finalizeOutput(ctx, selectedFrames) {
|
|
|
758
777
|
await (0, import_promises3.rename)(stagingDir, outputPath);
|
|
759
778
|
return outputFiles;
|
|
760
779
|
}
|
|
780
|
+
async function createSegmentWorkspace(parentWorkspacePath, segmentIndex) {
|
|
781
|
+
const segmentPath = (0, import_node_path4.join)(
|
|
782
|
+
parentWorkspacePath,
|
|
783
|
+
"segments",
|
|
784
|
+
String(segmentIndex)
|
|
785
|
+
);
|
|
786
|
+
await ensureDir((0, import_node_path4.join)(segmentPath, "frames"));
|
|
787
|
+
return segmentPath;
|
|
788
|
+
}
|
|
761
789
|
async function cleanupWorkspace(workspacePath) {
|
|
762
790
|
if (!workspacePath) return;
|
|
763
791
|
try {
|
|
@@ -818,7 +846,9 @@ function resolveOptions(options) {
|
|
|
818
846
|
quality: options.quality ?? DEFAULT_QUALITY,
|
|
819
847
|
iouThreshold: options.iouThreshold ?? IOU_THRESHOLD,
|
|
820
848
|
animationThreshold: options.animationThreshold ?? ANIMATION_FRAME_THRESHOLD,
|
|
821
|
-
debug: options.debug ?? false
|
|
849
|
+
debug: options.debug ?? false,
|
|
850
|
+
maxSegmentDuration: options.maxSegmentDuration ?? DEFAULT_MAX_SEGMENT_DURATION,
|
|
851
|
+
concurrency: options.concurrency ?? DEFAULT_SEGMENT_CONCURRENCY
|
|
822
852
|
};
|
|
823
853
|
}
|
|
824
854
|
async function resolveInput(options, workspacePath) {
|
|
@@ -1070,13 +1100,361 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
|
1070
1100
|
return pruneTo(syntheticEdges, survivingFrames, maxCount);
|
|
1071
1101
|
}
|
|
1072
1102
|
|
|
1103
|
+
// src/core/segmenter.ts
|
|
1104
|
+
var import_node_crypto = require("crypto");
|
|
1105
|
+
var import_node_path6 = require("path");
|
|
1106
|
+
|
|
1107
|
+
// src/utils/concurrency.ts
|
|
1108
|
+
function concurrencyLimit(limit) {
|
|
1109
|
+
limit = Math.max(1, limit);
|
|
1110
|
+
let active = 0;
|
|
1111
|
+
const queue = [];
|
|
1112
|
+
return async (fn) => {
|
|
1113
|
+
while (active >= limit) {
|
|
1114
|
+
await new Promise((resolve2) => queue.push(resolve2));
|
|
1115
|
+
}
|
|
1116
|
+
active++;
|
|
1117
|
+
try {
|
|
1118
|
+
return await fn();
|
|
1119
|
+
} finally {
|
|
1120
|
+
active--;
|
|
1121
|
+
queue.shift()?.();
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/core/segmenter.ts
|
|
1127
|
+
function shouldSegment(resolvedOptions, originalOptions) {
|
|
1128
|
+
if (resolvedOptions.mode === "frames") return false;
|
|
1129
|
+
if (originalOptions.mode === "file") {
|
|
1130
|
+
if (originalOptions.inputPath.toLowerCase().endsWith(".gif")) return false;
|
|
1131
|
+
}
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
function computeSegmentPlan(totalDuration, maxSegmentDuration, maxFrames, fps) {
|
|
1135
|
+
const effectiveFps = Math.max(0.5, Math.min(fps, maxFrames / totalDuration));
|
|
1136
|
+
if (totalDuration <= maxSegmentDuration) {
|
|
1137
|
+
return [
|
|
1138
|
+
{
|
|
1139
|
+
index: 0,
|
|
1140
|
+
startTime: 0,
|
|
1141
|
+
endTime: totalDuration,
|
|
1142
|
+
duration: totalDuration,
|
|
1143
|
+
allocatedFrames: Math.min(
|
|
1144
|
+
Math.ceil(effectiveFps * totalDuration),
|
|
1145
|
+
maxFrames
|
|
1146
|
+
),
|
|
1147
|
+
effectiveFps,
|
|
1148
|
+
overlapBefore: 0,
|
|
1149
|
+
overlapAfter: 0,
|
|
1150
|
+
extractStartTime: 0,
|
|
1151
|
+
extractDuration: totalDuration
|
|
1152
|
+
}
|
|
1153
|
+
];
|
|
1154
|
+
}
|
|
1155
|
+
const segmentCount = Math.ceil(totalDuration / maxSegmentDuration);
|
|
1156
|
+
const overlapTime = 1 / effectiveFps;
|
|
1157
|
+
const segments = [];
|
|
1158
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
1159
|
+
const startTime = i * maxSegmentDuration;
|
|
1160
|
+
const endTime = Math.min((i + 1) * maxSegmentDuration, totalDuration);
|
|
1161
|
+
const duration = endTime - startTime;
|
|
1162
|
+
const overlapBefore = i > 0 ? 1 : 0;
|
|
1163
|
+
const overlapAfter = i < segmentCount - 1 ? 1 : 0;
|
|
1164
|
+
const extractStartTime = Math.max(
|
|
1165
|
+
0,
|
|
1166
|
+
startTime - overlapBefore * overlapTime
|
|
1167
|
+
);
|
|
1168
|
+
const extractEndTime = Math.min(
|
|
1169
|
+
totalDuration,
|
|
1170
|
+
endTime + overlapAfter * overlapTime
|
|
1171
|
+
);
|
|
1172
|
+
const extractDuration = extractEndTime - extractStartTime;
|
|
1173
|
+
segments.push({
|
|
1174
|
+
index: i,
|
|
1175
|
+
startTime,
|
|
1176
|
+
endTime,
|
|
1177
|
+
duration,
|
|
1178
|
+
allocatedFrames: Math.ceil(effectiveFps * duration),
|
|
1179
|
+
effectiveFps,
|
|
1180
|
+
overlapBefore,
|
|
1181
|
+
overlapAfter,
|
|
1182
|
+
extractStartTime,
|
|
1183
|
+
extractDuration
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
const totalAllocated = segments.reduce(
|
|
1187
|
+
(sum, s) => sum + s.allocatedFrames,
|
|
1188
|
+
0
|
|
1189
|
+
);
|
|
1190
|
+
if (totalAllocated > maxFrames) {
|
|
1191
|
+
segments[segments.length - 1].allocatedFrames -= totalAllocated - maxFrames;
|
|
1192
|
+
}
|
|
1193
|
+
return segments;
|
|
1194
|
+
}
|
|
1195
|
+
function collectAllFrames(segmentResults) {
|
|
1196
|
+
const allFrames = [];
|
|
1197
|
+
for (const result of segmentResults) {
|
|
1198
|
+
for (const frame of result.frames) {
|
|
1199
|
+
allFrames.push({
|
|
1200
|
+
frame: {
|
|
1201
|
+
...frame,
|
|
1202
|
+
// Use extractStartTime for timestamp correction (Section 18 note 1)
|
|
1203
|
+
timestamp: frame.timestamp + result.segment.extractStartTime
|
|
1204
|
+
},
|
|
1205
|
+
segmentIndex: result.segment.index,
|
|
1206
|
+
localId: frame.id
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return allFrames;
|
|
1211
|
+
}
|
|
1212
|
+
function deduplicateFrames(frames, effectiveFps) {
|
|
1213
|
+
frames.sort((a, b) => a.frame.timestamp - b.frame.timestamp);
|
|
1214
|
+
const dupThreshold = 1 / (effectiveFps * 2);
|
|
1215
|
+
const unique = [];
|
|
1216
|
+
for (const entry of frames) {
|
|
1217
|
+
if (unique.length > 0) {
|
|
1218
|
+
const last = unique[unique.length - 1];
|
|
1219
|
+
if (Math.abs(entry.frame.timestamp - last.frame.timestamp) < dupThreshold) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
unique.push(entry);
|
|
1224
|
+
}
|
|
1225
|
+
return unique;
|
|
1226
|
+
}
|
|
1227
|
+
function remapFrameIds(uniqueFrames) {
|
|
1228
|
+
const globalIdMap = /* @__PURE__ */ new Map();
|
|
1229
|
+
const frames = uniqueFrames.map((entry, globalId) => {
|
|
1230
|
+
globalIdMap.set(`${entry.segmentIndex}:${entry.localId}`, globalId);
|
|
1231
|
+
return {
|
|
1232
|
+
id: globalId,
|
|
1233
|
+
timestamp: entry.frame.timestamp,
|
|
1234
|
+
extractPath: entry.frame.extractPath
|
|
1235
|
+
};
|
|
1236
|
+
});
|
|
1237
|
+
return { frames, globalIdMap };
|
|
1238
|
+
}
|
|
1239
|
+
function remapEdges(segmentResults, globalIdMap) {
|
|
1240
|
+
const edges = [];
|
|
1241
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1242
|
+
for (const result of segmentResults) {
|
|
1243
|
+
for (const edge of result.edges) {
|
|
1244
|
+
const newSourceId = globalIdMap.get(`${result.segment.index}:${edge.sourceId}`);
|
|
1245
|
+
const newTargetId = globalIdMap.get(`${result.segment.index}:${edge.targetId}`);
|
|
1246
|
+
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1247
|
+
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1248
|
+
const existingIdx = edgeMap.get(edgeKey);
|
|
1249
|
+
if (existingIdx !== void 0) {
|
|
1250
|
+
if (edges[existingIdx].score < edge.score) {
|
|
1251
|
+
edges[existingIdx] = { sourceId: newSourceId, targetId: newTargetId, score: edge.score };
|
|
1252
|
+
}
|
|
1253
|
+
} else {
|
|
1254
|
+
edgeMap.set(edgeKey, edges.length);
|
|
1255
|
+
edges.push({ sourceId: newSourceId, targetId: newTargetId, score: edge.score });
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return edges;
|
|
1260
|
+
}
|
|
1261
|
+
function remapAnimations(segmentResults, globalIdMap) {
|
|
1262
|
+
const animations = [];
|
|
1263
|
+
for (const result of segmentResults) {
|
|
1264
|
+
for (const anim of result.animations) {
|
|
1265
|
+
const newStartId = globalIdMap.get(`${result.segment.index}:${anim.startFrameId}`);
|
|
1266
|
+
const newEndId = globalIdMap.get(`${result.segment.index}:${anim.endFrameId}`);
|
|
1267
|
+
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1268
|
+
animations.push({ ...anim, startFrameId: newStartId, endFrameId: newEndId });
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return animations;
|
|
1272
|
+
}
|
|
1273
|
+
function mergeSegmentFrames(segmentResults) {
|
|
1274
|
+
if (segmentResults.length === 0) {
|
|
1275
|
+
return { frames: [], edges: [], animations: [] };
|
|
1276
|
+
}
|
|
1277
|
+
const effectiveFps = segmentResults[0].segment.effectiveFps;
|
|
1278
|
+
const allFrames = collectAllFrames(segmentResults);
|
|
1279
|
+
const uniqueFrames = deduplicateFrames(allFrames, effectiveFps);
|
|
1280
|
+
const { frames, globalIdMap } = remapFrameIds(uniqueFrames);
|
|
1281
|
+
const edges = remapEdges(segmentResults, globalIdMap);
|
|
1282
|
+
const animations = remapAnimations(segmentResults, globalIdMap);
|
|
1283
|
+
return { frames, edges, animations };
|
|
1284
|
+
}
|
|
1285
|
+
function buildSegmentContext(segment, frames, segmentWorkspacePath, resolvedOptions, onProgress) {
|
|
1286
|
+
return {
|
|
1287
|
+
options: {
|
|
1288
|
+
...resolvedOptions,
|
|
1289
|
+
fps: segment.effectiveFps,
|
|
1290
|
+
maxFrames: segment.allocatedFrames
|
|
1291
|
+
},
|
|
1292
|
+
workspacePath: segmentWorkspacePath,
|
|
1293
|
+
frames,
|
|
1294
|
+
graph: [],
|
|
1295
|
+
status: "ANALYZING",
|
|
1296
|
+
emitProgress: onProgress
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
async function processSegment(inputPath, segment, workspacePath, resolvedOptions, onProgress) {
|
|
1300
|
+
const framesDir = (0, import_node_path6.join)(workspacePath, "frames");
|
|
1301
|
+
const frames = await extractFramesForRange(
|
|
1302
|
+
inputPath,
|
|
1303
|
+
framesDir,
|
|
1304
|
+
segment.effectiveFps,
|
|
1305
|
+
resolvedOptions.scale,
|
|
1306
|
+
segment.extractStartTime,
|
|
1307
|
+
segment.extractDuration
|
|
1308
|
+
);
|
|
1309
|
+
if (frames.length < 2) {
|
|
1310
|
+
return { segment, frames, edges: [], animations: [] };
|
|
1311
|
+
}
|
|
1312
|
+
const ctx = buildSegmentContext(
|
|
1313
|
+
segment,
|
|
1314
|
+
frames,
|
|
1315
|
+
workspacePath,
|
|
1316
|
+
resolvedOptions,
|
|
1317
|
+
onProgress
|
|
1318
|
+
);
|
|
1319
|
+
const { edges, animations } = await analyzeFrames(ctx);
|
|
1320
|
+
return { segment, frames, edges, animations };
|
|
1321
|
+
}
|
|
1322
|
+
async function runSegmentedPipeline(options, resolvedOptions) {
|
|
1323
|
+
const pipelineStart = Date.now();
|
|
1324
|
+
const sessionId = (0, import_node_crypto.randomUUID)();
|
|
1325
|
+
let mainWorkspace = "";
|
|
1326
|
+
try {
|
|
1327
|
+
mainWorkspace = await createWorkspace(sessionId);
|
|
1328
|
+
logger.debug(`Segmented pipeline: workspace at ${mainWorkspace}`);
|
|
1329
|
+
const { resolvedInputPath } = await resolveInput(options, mainWorkspace);
|
|
1330
|
+
const inputPath = resolvedInputPath ?? resolvedOptions.inputPath;
|
|
1331
|
+
if (!inputPath) {
|
|
1332
|
+
throw new Error("No input path available for segmented pipeline");
|
|
1333
|
+
}
|
|
1334
|
+
const metadata = await getVideoMetadata(inputPath);
|
|
1335
|
+
const totalDuration = parseFloat(metadata.format?.duration ?? "0");
|
|
1336
|
+
if (totalDuration <= 0) {
|
|
1337
|
+
throw new Error(`Invalid video duration: ${totalDuration}`);
|
|
1338
|
+
}
|
|
1339
|
+
logger.debug(`Video duration: ${totalDuration}s`);
|
|
1340
|
+
const segments = computeSegmentPlan(
|
|
1341
|
+
totalDuration,
|
|
1342
|
+
resolvedOptions.maxSegmentDuration,
|
|
1343
|
+
resolvedOptions.maxFrames,
|
|
1344
|
+
resolvedOptions.fps
|
|
1345
|
+
);
|
|
1346
|
+
logger.debug(`Segment plan: ${segments.length} segments`);
|
|
1347
|
+
const limit = concurrencyLimit(resolvedOptions.concurrency);
|
|
1348
|
+
const segmentProgresses = new Array(segments.length).fill(0);
|
|
1349
|
+
const weights = segments.map((s) => s.duration / totalDuration);
|
|
1350
|
+
const emitOverallProgress = (phase) => {
|
|
1351
|
+
if (!options.onProgress) return;
|
|
1352
|
+
const overall = weights.reduce(
|
|
1353
|
+
(sum, w, i) => sum + w * (segmentProgresses[i] ?? 0),
|
|
1354
|
+
0
|
|
1355
|
+
);
|
|
1356
|
+
options.onProgress(phase, Math.min(100, overall));
|
|
1357
|
+
};
|
|
1358
|
+
options.onProgress?.("EXTRACTING", 0);
|
|
1359
|
+
const results = await Promise.all(
|
|
1360
|
+
segments.map(
|
|
1361
|
+
(segment) => limit(async () => {
|
|
1362
|
+
const segWorkspace = await createSegmentWorkspace(
|
|
1363
|
+
mainWorkspace,
|
|
1364
|
+
segment.index
|
|
1365
|
+
);
|
|
1366
|
+
const result = await processSegment(
|
|
1367
|
+
inputPath,
|
|
1368
|
+
segment,
|
|
1369
|
+
segWorkspace,
|
|
1370
|
+
resolvedOptions,
|
|
1371
|
+
(percent) => {
|
|
1372
|
+
segmentProgresses[segment.index] = percent;
|
|
1373
|
+
emitOverallProgress("ANALYZING");
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1376
|
+
return result;
|
|
1377
|
+
})
|
|
1378
|
+
)
|
|
1379
|
+
);
|
|
1380
|
+
options.onProgress?.("ANALYZING", 100);
|
|
1381
|
+
const { frames, edges, animations } = mergeSegmentFrames(results);
|
|
1382
|
+
logger.debug(
|
|
1383
|
+
`Merged: ${frames.length} frames, ${edges.length} edges, ${animations.length} animations`
|
|
1384
|
+
);
|
|
1385
|
+
options.onProgress?.("PRUNING", 0);
|
|
1386
|
+
const survivingIds = pruneByThresholdWithCap(
|
|
1387
|
+
edges,
|
|
1388
|
+
frames,
|
|
1389
|
+
resolvedOptions.threshold,
|
|
1390
|
+
resolvedOptions.count
|
|
1391
|
+
);
|
|
1392
|
+
const prunedFrames = frames.filter((f) => survivingIds.has(f.id));
|
|
1393
|
+
options.onProgress?.("PRUNING", 100);
|
|
1394
|
+
options.onProgress?.("FINALIZING", 0);
|
|
1395
|
+
const ctx = {
|
|
1396
|
+
options: resolvedOptions,
|
|
1397
|
+
workspacePath: mainWorkspace,
|
|
1398
|
+
frames,
|
|
1399
|
+
graph: edges,
|
|
1400
|
+
animations,
|
|
1401
|
+
status: "FINALIZING",
|
|
1402
|
+
emitProgress: (percent) => options.onProgress?.("FINALIZING", percent)
|
|
1403
|
+
};
|
|
1404
|
+
let outputFiles = [];
|
|
1405
|
+
let outputBuffers;
|
|
1406
|
+
if (resolvedOptions.mode === "buffer" || resolvedOptions.mode === "frames") {
|
|
1407
|
+
outputBuffers = await readFramesAsBuffers(
|
|
1408
|
+
prunedFrames,
|
|
1409
|
+
resolvedOptions.quality
|
|
1410
|
+
);
|
|
1411
|
+
} else {
|
|
1412
|
+
outputFiles = await finalizeOutput(ctx, prunedFrames);
|
|
1413
|
+
}
|
|
1414
|
+
options.onProgress?.("FINALIZING", 100);
|
|
1415
|
+
logger.success(
|
|
1416
|
+
`Segmented pipeline: ${prunedFrames.length} scenes from ${frames.length} frames (${segments.length} segments)`
|
|
1417
|
+
);
|
|
1418
|
+
return {
|
|
1419
|
+
success: true,
|
|
1420
|
+
originalFramesCount: frames.length,
|
|
1421
|
+
prunedFramesCount: prunedFrames.length,
|
|
1422
|
+
outputFiles,
|
|
1423
|
+
outputBuffers,
|
|
1424
|
+
animations,
|
|
1425
|
+
video: {
|
|
1426
|
+
originalDurationMs: totalDuration * 1e3,
|
|
1427
|
+
fps: resolvedOptions.fps,
|
|
1428
|
+
resolution: {
|
|
1429
|
+
width: resolvedOptions.scale,
|
|
1430
|
+
height: Math.round(resolvedOptions.scale * 9 / 16)
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
executionTimeMs: Date.now() - pipelineStart
|
|
1434
|
+
};
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1437
|
+
logger.error(`Segmented pipeline failed: ${err.message}`);
|
|
1438
|
+
throw err;
|
|
1439
|
+
} finally {
|
|
1440
|
+
if (!resolvedOptions.debug) {
|
|
1441
|
+
await cleanupWorkspace(mainWorkspace);
|
|
1442
|
+
} else {
|
|
1443
|
+
logger.debug(`Debug mode: workspace preserved at ${mainWorkspace}`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1073
1448
|
// src/core/orchestrator.ts
|
|
1074
1449
|
async function runPipeline(options) {
|
|
1075
|
-
const startTime = Date.now();
|
|
1076
|
-
const sessionId = (0, import_node_crypto.randomUUID)();
|
|
1077
1450
|
const debug = options.debug ?? false;
|
|
1078
1451
|
if (debug) setDebugMode(true);
|
|
1079
1452
|
const resolvedOptions = resolveOptions(options);
|
|
1453
|
+
if (shouldSegment(resolvedOptions, options)) {
|
|
1454
|
+
return runSegmentedPipeline(options, resolvedOptions);
|
|
1455
|
+
}
|
|
1456
|
+
const startTime = Date.now();
|
|
1457
|
+
const sessionId = (0, import_node_crypto2.randomUUID)();
|
|
1080
1458
|
const ctx = {
|
|
1081
1459
|
options: resolvedOptions,
|
|
1082
1460
|
workspacePath: "",
|