vidistill 0.4.2 → 0.4.3
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/index.js +268 -607
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -259,11 +259,11 @@ function showConfigBox(config) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
// src/commands/distill.ts
|
|
262
|
-
import { log as
|
|
262
|
+
import { log as log6, cancel as cancel2 } from "@clack/prompts";
|
|
263
263
|
import pc3 from "picocolors";
|
|
264
264
|
import { basename as basename3, extname as extname2, resolve, join as join4 } from "path";
|
|
265
265
|
import { existsSync as existsSync3, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
266
|
-
import {
|
|
266
|
+
import { readdir, rm } from "fs/promises";
|
|
267
267
|
|
|
268
268
|
// src/cli/prompts.ts
|
|
269
269
|
import { text, password, confirm, select, isCancel, cancel } from "@clack/prompts";
|
|
@@ -869,7 +869,7 @@ async function detectDuration(source) {
|
|
|
869
869
|
}
|
|
870
870
|
|
|
871
871
|
// src/core/pipeline.ts
|
|
872
|
-
import { log as
|
|
872
|
+
import { log as log4 } from "@clack/prompts";
|
|
873
873
|
|
|
874
874
|
// src/gemini/schemas.ts
|
|
875
875
|
import { Type } from "@google/genai";
|
|
@@ -2086,7 +2086,6 @@ var MODELS = {
|
|
|
2086
2086
|
};
|
|
2087
2087
|
|
|
2088
2088
|
// src/core/consensus.ts
|
|
2089
|
-
import { log as log4 } from "@clack/prompts";
|
|
2090
2089
|
function tokenize(content) {
|
|
2091
2090
|
const tokens = content.match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
2092
2091
|
return new Set(tokens);
|
|
@@ -2162,9 +2161,7 @@ async function runCodeConsensus(params) {
|
|
|
2162
2161
|
try {
|
|
2163
2162
|
const result = await runFn();
|
|
2164
2163
|
successfulRuns.push(result);
|
|
2165
|
-
} catch
|
|
2166
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2167
|
-
log4.warn(`consensus run ${i + 1}/${runs} failed: ${msg}`);
|
|
2164
|
+
} catch {
|
|
2168
2165
|
}
|
|
2169
2166
|
onProgress?.(i + 1, runs);
|
|
2170
2167
|
}
|
|
@@ -2250,9 +2247,7 @@ async function runLinkConsensus(params) {
|
|
|
2250
2247
|
try {
|
|
2251
2248
|
const result = await runFn();
|
|
2252
2249
|
successfulRuns.push(result);
|
|
2253
|
-
} catch
|
|
2254
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2255
|
-
log4.warn(`link consensus run ${i + 1}/${runs} failed: ${msg}`);
|
|
2250
|
+
} catch {
|
|
2256
2251
|
}
|
|
2257
2252
|
onProgress?.(i + 1, runs);
|
|
2258
2253
|
}
|
|
@@ -2460,39 +2455,26 @@ async function runPipeline(config) {
|
|
|
2460
2455
|
onProgress,
|
|
2461
2456
|
onWait,
|
|
2462
2457
|
isShuttingDown,
|
|
2463
|
-
lang
|
|
2464
|
-
preloadedResults,
|
|
2465
|
-
onPassComplete
|
|
2458
|
+
lang
|
|
2466
2459
|
} = config;
|
|
2467
2460
|
const errors = [];
|
|
2468
2461
|
const passesRun = [];
|
|
2469
2462
|
let videoProfile;
|
|
2470
2463
|
let strategy;
|
|
2471
|
-
|
|
2464
|
+
onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "running" });
|
|
2465
|
+
const pass0Attempt = await withRetry(
|
|
2466
|
+
() => rateLimiter.execute(() => runSceneAnalysis({ client, fileUri, mimeType, duration, model, lang }), { onWait }),
|
|
2467
|
+
"pass0"
|
|
2468
|
+
);
|
|
2469
|
+
if (pass0Attempt.error !== null) {
|
|
2470
|
+
log4.warn(pass0Attempt.error);
|
|
2471
|
+
errors.push(pass0Attempt.error);
|
|
2472
2472
|
videoProfile = DEFAULT_PROFILE;
|
|
2473
|
-
strategy = config.overrideStrategy;
|
|
2474
|
-
} else if (preloadedResults != null && "pass0-scene" in preloadedResults) {
|
|
2475
|
-
videoProfile = preloadedResults["pass0-scene"];
|
|
2476
|
-
strategy = config.overrideStrategy ?? preloadedResults["__strategy"] ?? determineStrategy(videoProfile);
|
|
2477
|
-
onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "done" });
|
|
2478
|
-
onPassComplete?.("pass0-scene", videoProfile);
|
|
2479
2473
|
} else {
|
|
2480
|
-
|
|
2481
|
-
const pass0Attempt = await withRetry(
|
|
2482
|
-
() => rateLimiter.execute(() => runSceneAnalysis({ client, fileUri, mimeType, duration, model, lang }), { onWait }),
|
|
2483
|
-
"pass0"
|
|
2484
|
-
);
|
|
2485
|
-
if (pass0Attempt.error !== null) {
|
|
2486
|
-
log5.warn(pass0Attempt.error);
|
|
2487
|
-
errors.push(pass0Attempt.error);
|
|
2488
|
-
videoProfile = DEFAULT_PROFILE;
|
|
2489
|
-
} else {
|
|
2490
|
-
videoProfile = pass0Attempt.result ?? DEFAULT_PROFILE;
|
|
2491
|
-
}
|
|
2492
|
-
strategy = determineStrategy(videoProfile);
|
|
2493
|
-
onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "done" });
|
|
2494
|
-
onPassComplete?.("pass0-scene", videoProfile);
|
|
2474
|
+
videoProfile = pass0Attempt.result ?? DEFAULT_PROFILE;
|
|
2495
2475
|
}
|
|
2476
|
+
strategy = determineStrategy(videoProfile);
|
|
2477
|
+
onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "done" });
|
|
2496
2478
|
const plan = createSegmentPlan(duration, {
|
|
2497
2479
|
segmentMinutes: strategy.segmentMinutes,
|
|
2498
2480
|
resolution: strategy.resolution
|
|
@@ -2505,23 +2487,7 @@ async function runPipeline(config) {
|
|
|
2505
2487
|
const callsPerSegment = 2 + (strategy.passes.includes("chat") ? linkConsensusRuns : 0) + (strategy.passes.includes("implicit") ? 1 : 0);
|
|
2506
2488
|
const postSegmentCalls = (strategy.passes.includes("people") ? 1 : 0) + (strategy.passes.includes("code") ? 3 : 0) + (strategy.passes.includes("synthesis") ? 1 : 0);
|
|
2507
2489
|
const totalSteps = n * callsPerSegment + postSegmentCalls;
|
|
2508
|
-
let
|
|
2509
|
-
if (preloadedResults != null) {
|
|
2510
|
-
for (let i = 0; i < n; i++) {
|
|
2511
|
-
if (`pass1-seg${i}` in preloadedResults) preloadedSteps++;
|
|
2512
|
-
if (`pass2-seg${i}` in preloadedResults) preloadedSteps++;
|
|
2513
|
-
if (strategy.passes.includes("chat")) {
|
|
2514
|
-
if (`pass3c-seg${i}` in preloadedResults) preloadedSteps += linkConsensusRuns;
|
|
2515
|
-
}
|
|
2516
|
-
if (strategy.passes.includes("implicit")) {
|
|
2517
|
-
if (`pass3d-seg${i}` in preloadedResults) preloadedSteps++;
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
if (strategy.passes.includes("people") && "pass3b-people" in preloadedResults) preloadedSteps++;
|
|
2521
|
-
if (strategy.passes.includes("code") && "pass3a" in preloadedResults) preloadedSteps += 3;
|
|
2522
|
-
if (strategy.passes.includes("synthesis") && "synthesis" in preloadedResults) preloadedSteps++;
|
|
2523
|
-
}
|
|
2524
|
-
let currentStep = preloadedSteps;
|
|
2490
|
+
let currentStep = 0;
|
|
2525
2491
|
let pass1RanOnce = false;
|
|
2526
2492
|
let pass2RanOnce = false;
|
|
2527
2493
|
let pass3cRanOnce = false;
|
|
@@ -2537,149 +2503,109 @@ async function runPipeline(config) {
|
|
|
2537
2503
|
const segment = segments[i];
|
|
2538
2504
|
onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "running", totalSteps });
|
|
2539
2505
|
let pass1 = null;
|
|
2540
|
-
const
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2506
|
+
const pass1Attempt = await withRetry(
|
|
2507
|
+
() => rateLimiter.execute(() => runTranscript({ client, fileUri, mimeType, segment, model, resolution, lang }), { onWait }),
|
|
2508
|
+
`segment ${i} pass1`
|
|
2509
|
+
);
|
|
2510
|
+
if (pass1Attempt.error !== null) {
|
|
2511
|
+
log4.warn(pass1Attempt.error);
|
|
2512
|
+
errors.push(pass1Attempt.error);
|
|
2547
2513
|
} else {
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
`segment ${i} pass1`
|
|
2551
|
-
);
|
|
2552
|
-
if (pass1Attempt.error !== null) {
|
|
2553
|
-
log5.warn(pass1Attempt.error);
|
|
2554
|
-
errors.push(pass1Attempt.error);
|
|
2555
|
-
} else {
|
|
2556
|
-
pass1 = pass1Attempt.result;
|
|
2557
|
-
pass1RanOnce = true;
|
|
2558
|
-
}
|
|
2559
|
-
currentStep++;
|
|
2560
|
-
onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2561
|
-
onPassComplete?.(pass1Key, pass1);
|
|
2514
|
+
pass1 = pass1Attempt.result;
|
|
2515
|
+
pass1RanOnce = true;
|
|
2562
2516
|
}
|
|
2517
|
+
currentStep++;
|
|
2518
|
+
onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2563
2519
|
onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "running", totalSteps });
|
|
2564
2520
|
let pass2 = null;
|
|
2565
|
-
const
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2521
|
+
const pass2Attempt = await withRetry(
|
|
2522
|
+
() => rateLimiter.execute(
|
|
2523
|
+
() => runVisual({
|
|
2524
|
+
client,
|
|
2525
|
+
fileUri,
|
|
2526
|
+
mimeType,
|
|
2527
|
+
segment,
|
|
2528
|
+
model,
|
|
2529
|
+
resolution,
|
|
2530
|
+
pass1Transcript: pass1 ?? void 0,
|
|
2531
|
+
lang
|
|
2532
|
+
}),
|
|
2533
|
+
{ onWait }
|
|
2534
|
+
),
|
|
2535
|
+
`segment ${i} pass2`
|
|
2536
|
+
);
|
|
2537
|
+
if (pass2Attempt.error !== null) {
|
|
2538
|
+
log4.warn(pass2Attempt.error);
|
|
2539
|
+
errors.push(pass2Attempt.error);
|
|
2572
2540
|
} else {
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2541
|
+
pass2 = pass2Attempt.result;
|
|
2542
|
+
pass2RanOnce = true;
|
|
2543
|
+
}
|
|
2544
|
+
currentStep++;
|
|
2545
|
+
onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2546
|
+
let pass3c;
|
|
2547
|
+
if (strategy.passes.includes("chat")) {
|
|
2548
|
+
onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: "running", totalSteps });
|
|
2549
|
+
const linkConsensusResult = await runLinkConsensus({
|
|
2550
|
+
config: { runs: linkConsensusRuns, minAgreement: 2 },
|
|
2551
|
+
runFn: () => rateLimiter.execute(
|
|
2552
|
+
() => runChatExtraction({
|
|
2576
2553
|
client,
|
|
2577
2554
|
fileUri,
|
|
2578
2555
|
mimeType,
|
|
2579
2556
|
segment,
|
|
2580
|
-
model,
|
|
2557
|
+
model: MODELS.flash,
|
|
2581
2558
|
resolution,
|
|
2582
|
-
|
|
2559
|
+
pass2Result: pass2 ?? void 0,
|
|
2583
2560
|
lang
|
|
2584
2561
|
}),
|
|
2585
2562
|
{ onWait }
|
|
2586
2563
|
),
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2564
|
+
onProgress: (run3, total) => {
|
|
2565
|
+
currentStep++;
|
|
2566
|
+
onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: run3 < total ? "running" : "done", currentStep, totalSteps });
|
|
2567
|
+
}
|
|
2568
|
+
});
|
|
2569
|
+
if (linkConsensusResult.runsCompleted === 0) {
|
|
2570
|
+
const errMsg = `segment ${i} pass3c: all link consensus runs failed`;
|
|
2571
|
+
log4.warn(errMsg);
|
|
2572
|
+
errors.push(errMsg);
|
|
2573
|
+
pass3c = null;
|
|
2592
2574
|
} else {
|
|
2593
|
-
|
|
2594
|
-
pass2RanOnce = true;
|
|
2595
|
-
}
|
|
2596
|
-
currentStep++;
|
|
2597
|
-
onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2598
|
-
onPassComplete?.(pass2Key, pass2);
|
|
2599
|
-
}
|
|
2600
|
-
let pass3c;
|
|
2601
|
-
if (strategy.passes.includes("chat")) {
|
|
2602
|
-
onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: "running", totalSteps });
|
|
2603
|
-
const pass3cKey = `pass3c-seg${i}`;
|
|
2604
|
-
if (preloadedResults != null && pass3cKey in preloadedResults) {
|
|
2605
|
-
pass3c = preloadedResults[pass3cKey];
|
|
2575
|
+
pass3c = linkConsensusResult.merged;
|
|
2606
2576
|
pass3cRanOnce = true;
|
|
2607
|
-
currentStep += linkConsensusRuns;
|
|
2608
|
-
onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2609
|
-
onPassComplete?.(pass3cKey, pass3c);
|
|
2610
|
-
} else {
|
|
2611
|
-
const linkConsensusResult = await runLinkConsensus({
|
|
2612
|
-
config: { runs: linkConsensusRuns, minAgreement: 2 },
|
|
2613
|
-
runFn: () => rateLimiter.execute(
|
|
2614
|
-
() => runChatExtraction({
|
|
2615
|
-
client,
|
|
2616
|
-
fileUri,
|
|
2617
|
-
mimeType,
|
|
2618
|
-
segment,
|
|
2619
|
-
model: MODELS.flash,
|
|
2620
|
-
resolution,
|
|
2621
|
-
pass2Result: pass2 ?? void 0,
|
|
2622
|
-
lang
|
|
2623
|
-
}),
|
|
2624
|
-
{ onWait }
|
|
2625
|
-
),
|
|
2626
|
-
onProgress: (run3, total) => {
|
|
2627
|
-
currentStep++;
|
|
2628
|
-
onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: run3 < total ? "running" : "done", currentStep, totalSteps });
|
|
2629
|
-
}
|
|
2630
|
-
});
|
|
2631
|
-
if (linkConsensusResult.runsCompleted === 0) {
|
|
2632
|
-
const errMsg = `segment ${i} pass3c: all link consensus runs failed`;
|
|
2633
|
-
log5.warn(errMsg);
|
|
2634
|
-
errors.push(errMsg);
|
|
2635
|
-
pass3c = null;
|
|
2636
|
-
} else {
|
|
2637
|
-
pass3c = linkConsensusResult.merged;
|
|
2638
|
-
pass3cRanOnce = true;
|
|
2639
|
-
}
|
|
2640
|
-
onPassComplete?.(pass3cKey, pass3c ?? null);
|
|
2641
2577
|
}
|
|
2642
2578
|
}
|
|
2643
2579
|
let pass3d;
|
|
2644
2580
|
if (strategy.passes.includes("implicit")) {
|
|
2645
2581
|
onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "running", totalSteps });
|
|
2646
|
-
const
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2582
|
+
const pass3dAttempt = await withRetry(
|
|
2583
|
+
() => rateLimiter.execute(
|
|
2584
|
+
() => runImplicitSignals({
|
|
2585
|
+
client,
|
|
2586
|
+
fileUri,
|
|
2587
|
+
mimeType,
|
|
2588
|
+
segment,
|
|
2589
|
+
model: MODELS.flash,
|
|
2590
|
+
resolution,
|
|
2591
|
+
pass1Result: pass1 ?? void 0,
|
|
2592
|
+
pass2Result: pass2 ?? void 0,
|
|
2593
|
+
lang
|
|
2594
|
+
}),
|
|
2595
|
+
{ onWait }
|
|
2596
|
+
),
|
|
2597
|
+
`segment ${i} pass3d`
|
|
2598
|
+
);
|
|
2599
|
+
if (pass3dAttempt.error !== null) {
|
|
2600
|
+
log4.warn(pass3dAttempt.error);
|
|
2601
|
+
errors.push(pass3dAttempt.error);
|
|
2602
|
+
pass3d = null;
|
|
2653
2603
|
} else {
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
() => runImplicitSignals({
|
|
2657
|
-
client,
|
|
2658
|
-
fileUri,
|
|
2659
|
-
mimeType,
|
|
2660
|
-
segment,
|
|
2661
|
-
model: MODELS.flash,
|
|
2662
|
-
resolution,
|
|
2663
|
-
pass1Result: pass1 ?? void 0,
|
|
2664
|
-
pass2Result: pass2 ?? void 0,
|
|
2665
|
-
lang
|
|
2666
|
-
}),
|
|
2667
|
-
{ onWait }
|
|
2668
|
-
),
|
|
2669
|
-
`segment ${i} pass3d`
|
|
2670
|
-
);
|
|
2671
|
-
if (pass3dAttempt.error !== null) {
|
|
2672
|
-
log5.warn(pass3dAttempt.error);
|
|
2673
|
-
errors.push(pass3dAttempt.error);
|
|
2674
|
-
pass3d = null;
|
|
2675
|
-
} else {
|
|
2676
|
-
pass3d = pass3dAttempt.result;
|
|
2677
|
-
pass3dRanOnce = true;
|
|
2678
|
-
}
|
|
2679
|
-
currentStep++;
|
|
2680
|
-
onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2681
|
-
onPassComplete?.(pass3dKey, pass3d ?? null);
|
|
2604
|
+
pass3d = pass3dAttempt.result;
|
|
2605
|
+
pass3dRanOnce = true;
|
|
2682
2606
|
}
|
|
2607
|
+
currentStep++;
|
|
2608
|
+
onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
|
|
2683
2609
|
}
|
|
2684
2610
|
results.push({ index: segment.index, pass1, pass2, pass3c, pass3d });
|
|
2685
2611
|
}
|
|
@@ -2709,136 +2635,106 @@ async function runPipeline(config) {
|
|
|
2709
2635
|
let peopleExtraction = null;
|
|
2710
2636
|
if (strategy.passes.includes("people")) {
|
|
2711
2637
|
onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "running", totalSteps });
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2638
|
+
const pass3bAttempt = await withRetry(
|
|
2639
|
+
() => rateLimiter.execute(
|
|
2640
|
+
() => runPeopleExtraction({
|
|
2641
|
+
client,
|
|
2642
|
+
fileUri,
|
|
2643
|
+
mimeType,
|
|
2644
|
+
model: MODELS.flash,
|
|
2645
|
+
pass1Results,
|
|
2646
|
+
lang
|
|
2647
|
+
}),
|
|
2648
|
+
{ onWait }
|
|
2649
|
+
),
|
|
2650
|
+
"pass3b"
|
|
2651
|
+
);
|
|
2652
|
+
if (pass3bAttempt.error !== null) {
|
|
2653
|
+
log4.warn(pass3bAttempt.error);
|
|
2654
|
+
errors.push(pass3bAttempt.error);
|
|
2719
2655
|
} else {
|
|
2720
|
-
|
|
2721
|
-
() => rateLimiter.execute(
|
|
2722
|
-
() => runPeopleExtraction({
|
|
2723
|
-
client,
|
|
2724
|
-
fileUri,
|
|
2725
|
-
mimeType,
|
|
2726
|
-
model: MODELS.flash,
|
|
2727
|
-
pass1Results,
|
|
2728
|
-
lang
|
|
2729
|
-
}),
|
|
2730
|
-
{ onWait }
|
|
2731
|
-
),
|
|
2732
|
-
"pass3b"
|
|
2733
|
-
);
|
|
2734
|
-
if (pass3bAttempt.error !== null) {
|
|
2735
|
-
log5.warn(pass3bAttempt.error);
|
|
2736
|
-
errors.push(pass3bAttempt.error);
|
|
2737
|
-
} else {
|
|
2738
|
-
peopleExtraction = pass3bAttempt.result;
|
|
2739
|
-
}
|
|
2740
|
-
currentStep++;
|
|
2741
|
-
onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
|
|
2742
|
-
onPassComplete?.(pass3bKey, peopleExtraction);
|
|
2743
|
-
if (peopleExtraction !== null) passesRun.push("pass3b");
|
|
2656
|
+
peopleExtraction = pass3bAttempt.result;
|
|
2744
2657
|
}
|
|
2658
|
+
currentStep++;
|
|
2659
|
+
onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
|
|
2660
|
+
if (peopleExtraction !== null) passesRun.push("pass3b");
|
|
2745
2661
|
}
|
|
2746
2662
|
let codeReconstruction = null;
|
|
2747
2663
|
let uncertainCodeFiles;
|
|
2748
2664
|
if (strategy.passes.includes("code")) {
|
|
2749
|
-
const
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2665
|
+
const consensusConfig = { runs: 3, minAgreement: 2 };
|
|
2666
|
+
const consensusResult = await runCodeConsensus({
|
|
2667
|
+
config: consensusConfig,
|
|
2668
|
+
runFn: () => rateLimiter.execute(
|
|
2669
|
+
() => runCodeReconstruction({
|
|
2670
|
+
client,
|
|
2671
|
+
fileUri,
|
|
2672
|
+
mimeType,
|
|
2673
|
+
duration,
|
|
2674
|
+
model: MODELS.pro,
|
|
2675
|
+
resolution,
|
|
2676
|
+
pass1Results,
|
|
2677
|
+
pass2Results,
|
|
2678
|
+
lang
|
|
2679
|
+
}),
|
|
2680
|
+
{ onWait }
|
|
2681
|
+
),
|
|
2682
|
+
pass2Results,
|
|
2683
|
+
onProgress: (run3, total) => {
|
|
2684
|
+
onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "running", totalSteps });
|
|
2685
|
+
currentStep++;
|
|
2686
|
+
onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "done", currentStep, totalSteps });
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
if (consensusResult.runsCompleted === 0) {
|
|
2690
|
+
const errMsg = "pass3a: all consensus runs failed";
|
|
2691
|
+
log4.warn(errMsg);
|
|
2692
|
+
errors.push(errMsg);
|
|
2756
2693
|
} else {
|
|
2757
|
-
const
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
runFn: () => rateLimiter.execute(
|
|
2761
|
-
() => runCodeReconstruction({
|
|
2762
|
-
client,
|
|
2763
|
-
fileUri,
|
|
2764
|
-
mimeType,
|
|
2765
|
-
duration,
|
|
2766
|
-
model: MODELS.pro,
|
|
2767
|
-
resolution,
|
|
2768
|
-
pass1Results,
|
|
2769
|
-
pass2Results,
|
|
2770
|
-
lang
|
|
2771
|
-
}),
|
|
2772
|
-
{ onWait }
|
|
2773
|
-
),
|
|
2774
|
-
pass2Results,
|
|
2775
|
-
onProgress: (run3, total) => {
|
|
2776
|
-
onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "running", totalSteps });
|
|
2777
|
-
currentStep++;
|
|
2778
|
-
onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "done", currentStep, totalSteps });
|
|
2779
|
-
}
|
|
2694
|
+
const validationResult = validateCodeReconstruction({
|
|
2695
|
+
consensusResult,
|
|
2696
|
+
pass2Results
|
|
2780
2697
|
});
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
});
|
|
2790
|
-
const allFiles = [...validationResult.confirmed, ...validationResult.uncertain];
|
|
2791
|
-
if (allFiles.length > 0) {
|
|
2792
|
-
codeReconstruction = {
|
|
2793
|
-
files: allFiles,
|
|
2794
|
-
dependencies_mentioned: consensusResult.mergedDependencies,
|
|
2795
|
-
build_commands: consensusResult.mergedBuildCommands
|
|
2796
|
-
};
|
|
2797
|
-
uncertainCodeFiles = validationResult.uncertain.map((f) => f.filename);
|
|
2798
|
-
}
|
|
2698
|
+
const allFiles = [...validationResult.confirmed, ...validationResult.uncertain];
|
|
2699
|
+
if (allFiles.length > 0) {
|
|
2700
|
+
codeReconstruction = {
|
|
2701
|
+
files: allFiles,
|
|
2702
|
+
dependencies_mentioned: consensusResult.mergedDependencies,
|
|
2703
|
+
build_commands: consensusResult.mergedBuildCommands
|
|
2704
|
+
};
|
|
2705
|
+
uncertainCodeFiles = validationResult.uncertain.map((f) => f.filename);
|
|
2799
2706
|
}
|
|
2800
|
-
onPassComplete?.(pass3aKey, codeReconstruction);
|
|
2801
|
-
if (codeReconstruction !== null) passesRun.push("pass3a");
|
|
2802
2707
|
}
|
|
2708
|
+
if (codeReconstruction !== null) passesRun.push("pass3a");
|
|
2803
2709
|
}
|
|
2804
2710
|
let synthesisResult;
|
|
2805
2711
|
if (strategy.passes.includes("synthesis")) {
|
|
2806
2712
|
onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "running", totalSteps });
|
|
2807
|
-
const
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2713
|
+
const synthAttempt = await withRetry(
|
|
2714
|
+
() => rateLimiter.execute(
|
|
2715
|
+
() => runSynthesis({
|
|
2716
|
+
client,
|
|
2717
|
+
model: MODELS.pro,
|
|
2718
|
+
segmentResults: results,
|
|
2719
|
+
videoProfile,
|
|
2720
|
+
peopleExtraction,
|
|
2721
|
+
codeReconstruction,
|
|
2722
|
+
context: config.context,
|
|
2723
|
+
lang
|
|
2724
|
+
}),
|
|
2725
|
+
{ onWait }
|
|
2726
|
+
),
|
|
2727
|
+
"synthesis"
|
|
2728
|
+
);
|
|
2729
|
+
if (synthAttempt.error !== null) {
|
|
2730
|
+
log4.warn(synthAttempt.error);
|
|
2731
|
+
errors.push(synthAttempt.error);
|
|
2814
2732
|
} else {
|
|
2815
|
-
|
|
2816
|
-
() => rateLimiter.execute(
|
|
2817
|
-
() => runSynthesis({
|
|
2818
|
-
client,
|
|
2819
|
-
model: MODELS.pro,
|
|
2820
|
-
segmentResults: results,
|
|
2821
|
-
videoProfile,
|
|
2822
|
-
peopleExtraction,
|
|
2823
|
-
codeReconstruction,
|
|
2824
|
-
context: config.context,
|
|
2825
|
-
lang
|
|
2826
|
-
}),
|
|
2827
|
-
{ onWait }
|
|
2828
|
-
),
|
|
2829
|
-
"synthesis"
|
|
2830
|
-
);
|
|
2831
|
-
if (synthAttempt.error !== null) {
|
|
2832
|
-
log5.warn(synthAttempt.error);
|
|
2833
|
-
errors.push(synthAttempt.error);
|
|
2834
|
-
} else {
|
|
2835
|
-
synthesisResult = synthAttempt.result ?? void 0;
|
|
2836
|
-
}
|
|
2837
|
-
currentStep++;
|
|
2838
|
-
onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
|
|
2839
|
-
onPassComplete?.(synthKey, synthesisResult ?? null);
|
|
2840
|
-
if (synthesisResult !== void 0) passesRun.push("synthesis");
|
|
2733
|
+
synthesisResult = synthAttempt.result ?? void 0;
|
|
2841
2734
|
}
|
|
2735
|
+
currentStep++;
|
|
2736
|
+
onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
|
|
2737
|
+
if (synthesisResult !== void 0) passesRun.push("synthesis");
|
|
2842
2738
|
}
|
|
2843
2739
|
return {
|
|
2844
2740
|
segments: results,
|
|
@@ -4543,7 +4439,7 @@ async function reRenderWithSpeakerMapping(params) {
|
|
|
4543
4439
|
}
|
|
4544
4440
|
|
|
4545
4441
|
// src/core/shutdown.ts
|
|
4546
|
-
import { log as
|
|
4442
|
+
import { log as log5 } from "@clack/prompts";
|
|
4547
4443
|
function createShutdownHandler(params) {
|
|
4548
4444
|
const { client, uploadedFileNames } = params;
|
|
4549
4445
|
let shuttingDown = false;
|
|
@@ -4559,10 +4455,10 @@ function createShutdownHandler(params) {
|
|
|
4559
4455
|
}
|
|
4560
4456
|
shuttingDown = true;
|
|
4561
4457
|
if (hasProgress) {
|
|
4562
|
-
|
|
4563
|
-
|
|
4458
|
+
log5.warn(`Interrupted \u2014 progress saved (${progressCurrentStep}/${progressTotalSteps} steps)`);
|
|
4459
|
+
log5.info(`Resume: vidistill ${params.source} -o ${params.outputDir}/`);
|
|
4564
4460
|
} else {
|
|
4565
|
-
|
|
4461
|
+
log5.warn("Interrupted");
|
|
4566
4462
|
}
|
|
4567
4463
|
const forceExitHandler = () => {
|
|
4568
4464
|
process.exit(1);
|
|
@@ -4609,135 +4505,7 @@ function createShutdownHandler(params) {
|
|
|
4609
4505
|
};
|
|
4610
4506
|
}
|
|
4611
4507
|
|
|
4612
|
-
// src/cli/speaker-naming.ts
|
|
4613
|
-
import { log as log7, text as text2, confirm as confirm2, isCancel as isCancel2, cancel as cancel2 } from "@clack/prompts";
|
|
4614
|
-
function buildSpeakerContext(pipelineResult) {
|
|
4615
|
-
const { segments, peopleExtraction } = pipelineResult;
|
|
4616
|
-
const speakerMap = /* @__PURE__ */ new Map();
|
|
4617
|
-
for (const seg of segments) {
|
|
4618
|
-
if (seg.pass1 == null) continue;
|
|
4619
|
-
for (const info of seg.pass1.speaker_summary) {
|
|
4620
|
-
if (!info.speaker_id) continue;
|
|
4621
|
-
const existing = speakerMap.get(info.speaker_id);
|
|
4622
|
-
if (existing == null) {
|
|
4623
|
-
speakerMap.set(info.speaker_id, { totalEntries: 0, descriptions: [] });
|
|
4624
|
-
}
|
|
4625
|
-
const entry = speakerMap.get(info.speaker_id);
|
|
4626
|
-
const entryCount = seg.pass1.transcript_entries.filter(
|
|
4627
|
-
(e) => e.speaker === info.speaker_id
|
|
4628
|
-
).length;
|
|
4629
|
-
entry.totalEntries += entryCount;
|
|
4630
|
-
if (info.description && info.description.trim().length > 0) {
|
|
4631
|
-
entry.descriptions.push(info.description.trim());
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
}
|
|
4635
|
-
const participantsByLabel = /* @__PURE__ */ new Map();
|
|
4636
|
-
if (peopleExtraction?.participants != null) {
|
|
4637
|
-
for (const p of peopleExtraction.participants) {
|
|
4638
|
-
if (/^SPEAKER_\d+$/.test(p.name)) {
|
|
4639
|
-
participantsByLabel.set(p.name, p);
|
|
4640
|
-
}
|
|
4641
|
-
}
|
|
4642
|
-
}
|
|
4643
|
-
const contexts = [];
|
|
4644
|
-
for (const [label, data] of speakerMap.entries()) {
|
|
4645
|
-
const participant = participantsByLabel.get(label);
|
|
4646
|
-
let description = "";
|
|
4647
|
-
if (participant != null) {
|
|
4648
|
-
if (participant.contributions.length > 0) {
|
|
4649
|
-
description = participant.contributions[0];
|
|
4650
|
-
} else if (participant.role) {
|
|
4651
|
-
description = participant.role;
|
|
4652
|
-
}
|
|
4653
|
-
}
|
|
4654
|
-
if (!description && data.descriptions.length > 0) {
|
|
4655
|
-
description = data.descriptions[0];
|
|
4656
|
-
}
|
|
4657
|
-
contexts.push({
|
|
4658
|
-
label,
|
|
4659
|
-
description,
|
|
4660
|
-
speakingSeconds: data.totalEntries
|
|
4661
|
-
// Using entry count as proxy
|
|
4662
|
-
});
|
|
4663
|
-
}
|
|
4664
|
-
contexts.sort((a, b) => {
|
|
4665
|
-
if (b.speakingSeconds !== a.speakingSeconds) return b.speakingSeconds - a.speakingSeconds;
|
|
4666
|
-
return a.label.localeCompare(b.label);
|
|
4667
|
-
});
|
|
4668
|
-
return contexts;
|
|
4669
|
-
}
|
|
4670
|
-
async function detectAndPromptMerges(mapping) {
|
|
4671
|
-
const byName = /* @__PURE__ */ new Map();
|
|
4672
|
-
for (const [label, name] of Object.entries(mapping)) {
|
|
4673
|
-
const existing = byName.get(name);
|
|
4674
|
-
if (existing == null) {
|
|
4675
|
-
byName.set(name, [label]);
|
|
4676
|
-
} else {
|
|
4677
|
-
existing.push(label);
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
|
-
const declinedMerges = [];
|
|
4681
|
-
const resultMapping = { ...mapping };
|
|
4682
|
-
for (const [name, labels] of byName.entries()) {
|
|
4683
|
-
if (labels.length < 2) continue;
|
|
4684
|
-
const sorted = [...labels].sort((a, b) => a.localeCompare(b));
|
|
4685
|
-
const primary = sorted[0];
|
|
4686
|
-
const secondaries = sorted.slice(1);
|
|
4687
|
-
for (const secondary of secondaries) {
|
|
4688
|
-
const answer = await confirm2({
|
|
4689
|
-
message: `You assigned '${name}' to both ${primary} and ${secondary}. Merge ${secondary} into ${primary} (${name})?`
|
|
4690
|
-
});
|
|
4691
|
-
if (isCancel2(answer)) {
|
|
4692
|
-
cancel2("Speaker naming cancelled.");
|
|
4693
|
-
return null;
|
|
4694
|
-
}
|
|
4695
|
-
if (answer === true) {
|
|
4696
|
-
} else {
|
|
4697
|
-
declinedMerges.push([primary, secondary]);
|
|
4698
|
-
}
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
return { mapping: resultMapping, declinedMerges };
|
|
4702
|
-
}
|
|
4703
|
-
async function promptForSpeakers(speakers) {
|
|
4704
|
-
const mapping = {};
|
|
4705
|
-
for (const speaker of speakers) {
|
|
4706
|
-
const descPart = speaker.description ? ` \u2014 ${speaker.description}` : "";
|
|
4707
|
-
const value = await text2({
|
|
4708
|
-
message: `Name for ${speaker.label}${descPart} [${speaker.speakingSeconds} entries]:`,
|
|
4709
|
-
placeholder: "Enter name or press Enter to skip"
|
|
4710
|
-
});
|
|
4711
|
-
if (isCancel2(value) || typeof value !== "string") {
|
|
4712
|
-
cancel2("Speaker naming cancelled.");
|
|
4713
|
-
return null;
|
|
4714
|
-
}
|
|
4715
|
-
const trimmed = value.trim();
|
|
4716
|
-
if (trimmed.length > 0) {
|
|
4717
|
-
mapping[speaker.label] = trimmed;
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
return mapping;
|
|
4721
|
-
}
|
|
4722
|
-
async function promptSpeakerNames(pipelineResult) {
|
|
4723
|
-
try {
|
|
4724
|
-
const allSpeakers = buildSpeakerContext(pipelineResult);
|
|
4725
|
-
if (allSpeakers.length <= 1) {
|
|
4726
|
-
return null;
|
|
4727
|
-
}
|
|
4728
|
-
log7.info(`${String(allSpeakers.length)} speakers detected. Enter names to personalize output (or press Enter to skip each).`);
|
|
4729
|
-
const mapping = await promptForSpeakers(allSpeakers);
|
|
4730
|
-
if (mapping == null) return null;
|
|
4731
|
-
const mergeResult = await detectAndPromptMerges(mapping);
|
|
4732
|
-
if (mergeResult == null) return null;
|
|
4733
|
-
return mergeResult;
|
|
4734
|
-
} catch {
|
|
4735
|
-
return null;
|
|
4736
|
-
}
|
|
4737
|
-
}
|
|
4738
|
-
|
|
4739
4508
|
// src/commands/distill.ts
|
|
4740
|
-
var PROGRESS_SCHEMA_VERSION = 1;
|
|
4741
4509
|
function peekIsAudio(filePath) {
|
|
4742
4510
|
if (!existsSync3(filePath)) return false;
|
|
4743
4511
|
try {
|
|
@@ -4765,62 +4533,7 @@ function peekIsAudio(filePath) {
|
|
|
4765
4533
|
return false;
|
|
4766
4534
|
}
|
|
4767
4535
|
}
|
|
4768
|
-
function parseSemver(version2) {
|
|
4769
|
-
const parts = version2.split(".").map(Number);
|
|
4770
|
-
return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
|
|
4771
|
-
}
|
|
4772
|
-
async function readProgressFile(outputDir) {
|
|
4773
|
-
const progressPath = join4(outputDir, "progress.json");
|
|
4774
|
-
if (!existsSync3(progressPath)) return null;
|
|
4775
|
-
try {
|
|
4776
|
-
const raw = await readFile2(progressPath, "utf8");
|
|
4777
|
-
const parsed = JSON.parse(raw);
|
|
4778
|
-
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
4779
|
-
const obj = parsed;
|
|
4780
|
-
if (typeof obj["schemaVersion"] !== "number" || typeof obj["vidistillVersion"] !== "string" || typeof obj["completedPasses"] !== "object") return null;
|
|
4781
|
-
return parsed;
|
|
4782
|
-
} catch {
|
|
4783
|
-
return null;
|
|
4784
|
-
}
|
|
4785
|
-
}
|
|
4786
|
-
async function writeProgressFile(outputDir, progressFile) {
|
|
4787
|
-
await mkdir2(outputDir, { recursive: true });
|
|
4788
|
-
const progressPath = join4(outputDir, "progress.json");
|
|
4789
|
-
await writeFile2(progressPath, JSON.stringify(progressFile, null, 2), "utf8");
|
|
4790
|
-
}
|
|
4791
|
-
async function validateCompletedPasses(outputDir, completedPasses) {
|
|
4792
|
-
const invalid = [];
|
|
4793
|
-
for (const [passKey, rawFile] of Object.entries(completedPasses)) {
|
|
4794
|
-
const rawPath = join4(outputDir, "raw", `${rawFile}.json`);
|
|
4795
|
-
if (!existsSync3(rawPath)) {
|
|
4796
|
-
invalid.push(passKey);
|
|
4797
|
-
continue;
|
|
4798
|
-
}
|
|
4799
|
-
try {
|
|
4800
|
-
const content = await readFile2(rawPath, "utf8");
|
|
4801
|
-
JSON.parse(content);
|
|
4802
|
-
} catch {
|
|
4803
|
-
invalid.push(passKey);
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
return invalid;
|
|
4807
|
-
}
|
|
4808
|
-
async function loadPreloadedResults(outputDir, completedPasses) {
|
|
4809
|
-
const preloaded = {};
|
|
4810
|
-
for (const [passKey, rawFile] of Object.entries(completedPasses)) {
|
|
4811
|
-
const rawPath = join4(outputDir, "raw", `${rawFile}.json`);
|
|
4812
|
-
try {
|
|
4813
|
-
const content = await readFile2(rawPath, "utf8");
|
|
4814
|
-
const parsed = JSON.parse(content);
|
|
4815
|
-
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
4816
|
-
preloaded[passKey] = parsed;
|
|
4817
|
-
} catch {
|
|
4818
|
-
}
|
|
4819
|
-
}
|
|
4820
|
-
return preloaded;
|
|
4821
|
-
}
|
|
4822
4536
|
async function clearDirectory(dir) {
|
|
4823
|
-
const { readdir } = await import("fs/promises");
|
|
4824
4537
|
const entries = await readdir(dir);
|
|
4825
4538
|
await Promise.all(
|
|
4826
4539
|
entries.map((entry) => rm(join4(dir, entry), { recursive: true, force: true }))
|
|
@@ -4855,7 +4568,7 @@ async function runDistill(args) {
|
|
|
4855
4568
|
context = await promptContext();
|
|
4856
4569
|
break;
|
|
4857
4570
|
case "cancel":
|
|
4858
|
-
|
|
4571
|
+
cancel2("Cancelled.");
|
|
4859
4572
|
process.exit(0);
|
|
4860
4573
|
}
|
|
4861
4574
|
}
|
|
@@ -4897,69 +4610,8 @@ async function runDistill(args) {
|
|
|
4897
4610
|
const outputDir = resolve(args.output);
|
|
4898
4611
|
const slug = slugify(videoTitle);
|
|
4899
4612
|
const finalOutputDir = `${outputDir}/${slug}`;
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
const progressFile = await readProgressFile(finalOutputDir);
|
|
4903
|
-
if (progressFile != null) {
|
|
4904
|
-
if (progressFile.schemaVersion > PROGRESS_SCHEMA_VERSION) {
|
|
4905
|
-
log8.error(
|
|
4906
|
-
`This progress file was created by a newer version of vidistill. Please upgrade. (file schemaVersion: ${String(progressFile.schemaVersion)}, current: ${String(PROGRESS_SCHEMA_VERSION)})`
|
|
4907
|
-
);
|
|
4908
|
-
process.exit(1);
|
|
4909
|
-
}
|
|
4910
|
-
const currentVer = parseSemver("0.4.2");
|
|
4911
|
-
const fileVer = parseSemver(progressFile.vidistillVersion);
|
|
4912
|
-
const sameMajorMinor = currentVer[0] === fileVer[0] && currentVer[1] === fileVer[1];
|
|
4913
|
-
let versionWarning = null;
|
|
4914
|
-
if (!sameMajorMinor) {
|
|
4915
|
-
versionWarning = `Progress file was created by v${progressFile.vidistillVersion}, current is v${"0.4.2"}.`;
|
|
4916
|
-
}
|
|
4917
|
-
const invalidPasses = await validateCompletedPasses(finalOutputDir, progressFile.completedPasses);
|
|
4918
|
-
if (invalidPasses.length > 0) {
|
|
4919
|
-
log8.warn(`Some completed pass files are missing or corrupt: ${invalidPasses.join(", ")}`);
|
|
4920
|
-
}
|
|
4921
|
-
const validPasses = Object.fromEntries(
|
|
4922
|
-
Object.entries(progressFile.completedPasses).filter(([key]) => !invalidPasses.includes(key))
|
|
4923
|
-
);
|
|
4924
|
-
const completedCount = Object.keys(validPasses).length;
|
|
4925
|
-
if (versionWarning != null) {
|
|
4926
|
-
log8.warn(versionWarning);
|
|
4927
|
-
}
|
|
4928
|
-
if (completedCount === 0) {
|
|
4929
|
-
log8.info("No valid passes to resume from. Starting fresh.");
|
|
4930
|
-
if (existsSync3(finalOutputDir)) {
|
|
4931
|
-
await clearDirectory(finalOutputDir);
|
|
4932
|
-
}
|
|
4933
|
-
} else {
|
|
4934
|
-
const resumeChoice = await select2({
|
|
4935
|
-
message: `Found incomplete run (${String(completedCount)} passes done). Resume?`,
|
|
4936
|
-
options: [
|
|
4937
|
-
{ value: "resume", label: "Resume" },
|
|
4938
|
-
{ value: "fresh", label: "Fresh start" },
|
|
4939
|
-
{ value: "cancel", label: "Cancel" }
|
|
4940
|
-
]
|
|
4941
|
-
});
|
|
4942
|
-
if (isCancel3(resumeChoice)) {
|
|
4943
|
-
cancel3("Cancelled.");
|
|
4944
|
-
process.exit(0);
|
|
4945
|
-
}
|
|
4946
|
-
if (resumeChoice === "cancel") {
|
|
4947
|
-
cancel3("Cancelled.");
|
|
4948
|
-
process.exit(0);
|
|
4949
|
-
} else if (resumeChoice === "fresh") {
|
|
4950
|
-
if (existsSync3(finalOutputDir)) {
|
|
4951
|
-
await clearDirectory(finalOutputDir);
|
|
4952
|
-
}
|
|
4953
|
-
} else {
|
|
4954
|
-
preloadedResults = await loadPreloadedResults(finalOutputDir, validPasses);
|
|
4955
|
-
if (progressFile.strategy != null) {
|
|
4956
|
-
overrideStrategy = progressFile.strategy;
|
|
4957
|
-
if (progressFile.videoProfile != null && !("pass0-scene" in preloadedResults)) {
|
|
4958
|
-
preloadedResults["pass0-scene"] = progressFile.videoProfile;
|
|
4959
|
-
}
|
|
4960
|
-
}
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4613
|
+
if (existsSync3(finalOutputDir)) {
|
|
4614
|
+
await clearDirectory(finalOutputDir);
|
|
4963
4615
|
}
|
|
4964
4616
|
const shutdownHandler = createShutdownHandler({
|
|
4965
4617
|
client,
|
|
@@ -4973,13 +4625,6 @@ async function runDistill(args) {
|
|
|
4973
4625
|
shutdownHandler.register();
|
|
4974
4626
|
const rateLimiter = new RateLimiter();
|
|
4975
4627
|
const progress2 = createProgressDisplay();
|
|
4976
|
-
const progressState = {
|
|
4977
|
-
schemaVersion: PROGRESS_SCHEMA_VERSION,
|
|
4978
|
-
vidistillVersion: "0.4.2",
|
|
4979
|
-
completedPasses: progressFile != null && preloadedResults != null ? { ...progressFile.completedPasses } : {},
|
|
4980
|
-
videoProfile: progressFile?.videoProfile,
|
|
4981
|
-
strategy: progressFile?.strategy
|
|
4982
|
-
};
|
|
4983
4628
|
const startTime = Date.now();
|
|
4984
4629
|
const pipelineResult = await runPipeline({
|
|
4985
4630
|
client,
|
|
@@ -4997,19 +4642,7 @@ async function runDistill(args) {
|
|
|
4997
4642
|
}
|
|
4998
4643
|
},
|
|
4999
4644
|
onWait: (delayMs) => progress2.onWait(delayMs),
|
|
5000
|
-
isShuttingDown: () => shutdownHandler.isShuttingDown()
|
|
5001
|
-
preloadedResults,
|
|
5002
|
-
overrideStrategy,
|
|
5003
|
-
onPassComplete: (passKey, result) => {
|
|
5004
|
-
if (result == null) return;
|
|
5005
|
-
progressState.completedPasses[passKey] = passKey;
|
|
5006
|
-
if (passKey === "pass0-scene") {
|
|
5007
|
-
progressState.videoProfile = result;
|
|
5008
|
-
progressState.strategy = determineStrategy(result);
|
|
5009
|
-
}
|
|
5010
|
-
writeProgressFile(finalOutputDir, progressState).catch(() => {
|
|
5011
|
-
});
|
|
5012
|
-
}
|
|
4645
|
+
isShuttingDown: () => shutdownHandler.isShuttingDown()
|
|
5013
4646
|
});
|
|
5014
4647
|
const elapsedMs = Date.now() - startTime;
|
|
5015
4648
|
shutdownHandler.deregister();
|
|
@@ -5026,43 +4659,71 @@ async function runDistill(args) {
|
|
|
5026
4659
|
model,
|
|
5027
4660
|
processingTimeMs: elapsedMs
|
|
5028
4661
|
});
|
|
5029
|
-
const namingResult = await promptSpeakerNames(pipelineResult);
|
|
5030
|
-
if (namingResult != null && Object.keys(namingResult.mapping).length > 0) {
|
|
5031
|
-
await reRenderWithSpeakerMapping({
|
|
5032
|
-
outputDir: outputResult.outputDir,
|
|
5033
|
-
speakerMapping: namingResult.mapping,
|
|
5034
|
-
declinedMerges: namingResult.declinedMerges.length > 0 ? namingResult.declinedMerges : void 0
|
|
5035
|
-
});
|
|
5036
|
-
}
|
|
5037
4662
|
const elapsedSecs = Math.round(elapsedMs / 1e3);
|
|
5038
4663
|
const elapsedMins = Math.floor(elapsedSecs / 60);
|
|
5039
4664
|
const remainSecs = elapsedSecs % 60;
|
|
5040
4665
|
const elapsed = elapsedMins > 0 ? `${elapsedMins}m ${remainSecs}s` : `${remainSecs}s`;
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
if (pipelineResult.
|
|
5045
|
-
|
|
5046
|
-
} else if (pipelineResult.peopleExtraction?.participants != null && pipelineResult.peopleExtraction.participants.length > 1) {
|
|
5047
|
-
log8.info(pc3.dim("Tip: vidistill rename-speakers <dir> to assign real names"));
|
|
4666
|
+
log6.success(`Done in ${elapsed}`);
|
|
4667
|
+
log6.info(`Output: ${finalOutputDir}/`);
|
|
4668
|
+
log6.info(pc3.dim("Open guide.md for an overview"));
|
|
4669
|
+
if (pipelineResult.peopleExtraction?.participants != null && pipelineResult.peopleExtraction.participants.length > 1) {
|
|
4670
|
+
log6.info(pc3.dim("Tip: vidistill rename-speakers <dir> to assign real names"));
|
|
5048
4671
|
}
|
|
5049
4672
|
if (outputResult.errors.length > 0) {
|
|
5050
|
-
|
|
4673
|
+
log6.warn(`Output errors: ${pc3.yellow(String(outputResult.errors.length))}`);
|
|
5051
4674
|
for (const err of outputResult.errors) {
|
|
5052
|
-
|
|
4675
|
+
log6.warn(pc3.dim(` ${err}`));
|
|
5053
4676
|
}
|
|
5054
4677
|
}
|
|
5055
4678
|
}
|
|
5056
4679
|
|
|
5057
4680
|
// src/commands/mcp.ts
|
|
5058
|
-
import { log as
|
|
4681
|
+
import { log as log7 } from "@clack/prompts";
|
|
5059
4682
|
async function run(_args) {
|
|
5060
|
-
|
|
4683
|
+
log7.info("Not implemented yet.");
|
|
5061
4684
|
}
|
|
5062
4685
|
|
|
5063
4686
|
// src/commands/rename-speakers.ts
|
|
5064
4687
|
import { join as join5 } from "path";
|
|
5065
|
-
import { log as
|
|
4688
|
+
import { log as log9, text as text3, isCancel as isCancel3, cancel as cancel4 } from "@clack/prompts";
|
|
4689
|
+
|
|
4690
|
+
// src/cli/speaker-naming.ts
|
|
4691
|
+
import { log as log8, text as text2, confirm as confirm2, isCancel as isCancel2, cancel as cancel3 } from "@clack/prompts";
|
|
4692
|
+
async function detectAndPromptMerges(mapping) {
|
|
4693
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4694
|
+
for (const [label, name] of Object.entries(mapping)) {
|
|
4695
|
+
const existing = byName.get(name);
|
|
4696
|
+
if (existing == null) {
|
|
4697
|
+
byName.set(name, [label]);
|
|
4698
|
+
} else {
|
|
4699
|
+
existing.push(label);
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
const declinedMerges = [];
|
|
4703
|
+
const resultMapping = { ...mapping };
|
|
4704
|
+
for (const [name, labels] of byName.entries()) {
|
|
4705
|
+
if (labels.length < 2) continue;
|
|
4706
|
+
const sorted = [...labels].sort((a, b) => a.localeCompare(b));
|
|
4707
|
+
const primary = sorted[0];
|
|
4708
|
+
const secondaries = sorted.slice(1);
|
|
4709
|
+
for (const secondary of secondaries) {
|
|
4710
|
+
const answer = await confirm2({
|
|
4711
|
+
message: `You assigned '${name}' to both ${primary} and ${secondary}. Merge ${secondary} into ${primary} (${name})?`
|
|
4712
|
+
});
|
|
4713
|
+
if (isCancel2(answer)) {
|
|
4714
|
+
cancel3("Speaker naming cancelled.");
|
|
4715
|
+
return null;
|
|
4716
|
+
}
|
|
4717
|
+
if (answer === true) {
|
|
4718
|
+
} else {
|
|
4719
|
+
declinedMerges.push([primary, secondary]);
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
return { mapping: resultMapping, declinedMerges };
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
// src/commands/rename-speakers.ts
|
|
5066
4727
|
async function collectSpeakersFromRaw(rawDir) {
|
|
5067
4728
|
const speakerEntries = /* @__PURE__ */ new Map();
|
|
5068
4729
|
for (let n = 0; n < 1e3; n++) {
|
|
@@ -5186,19 +4847,19 @@ async function runList(outputDir) {
|
|
|
5186
4847
|
const metadataPath = join5(outputDir, "metadata.json");
|
|
5187
4848
|
const metadata = await readJsonFile(metadataPath);
|
|
5188
4849
|
if (metadata == null) {
|
|
5189
|
-
|
|
4850
|
+
log9.error("Not a vidistill output directory");
|
|
5190
4851
|
return;
|
|
5191
4852
|
}
|
|
5192
4853
|
const rawDir = join5(outputDir, "raw");
|
|
5193
4854
|
const speakers = await collectSpeakersFromRaw(rawDir);
|
|
5194
4855
|
const speakerMapping = metadata.speakerMapping ?? {};
|
|
5195
4856
|
if (speakers.length === 0 && Object.keys(speakerMapping).length === 0) {
|
|
5196
|
-
|
|
4857
|
+
log9.info("No speakers found.");
|
|
5197
4858
|
return;
|
|
5198
4859
|
}
|
|
5199
4860
|
const groups = groupSpeakersByExistingMapping(speakers, speakerMapping);
|
|
5200
4861
|
if (groups.length === 0) {
|
|
5201
|
-
|
|
4862
|
+
log9.info("No speakers found.");
|
|
5202
4863
|
return;
|
|
5203
4864
|
}
|
|
5204
4865
|
const lines = groups.map((group, idx) => {
|
|
@@ -5207,17 +4868,17 @@ async function runList(outputDir) {
|
|
|
5207
4868
|
const labelsStr = group.labels.join(", ");
|
|
5208
4869
|
return `${String(num)}. ${displayName} (${labelsStr}, ${String(group.totalEntries)} entries)`;
|
|
5209
4870
|
});
|
|
5210
|
-
|
|
4871
|
+
log9.info(lines.join("\n"));
|
|
5211
4872
|
}
|
|
5212
4873
|
async function runRename(outputDir, oldName, newName) {
|
|
5213
4874
|
if (newName.trim().length === 0) {
|
|
5214
|
-
|
|
4875
|
+
log9.error("New name cannot be empty. Use the interactive prompt to clear a mapping.");
|
|
5215
4876
|
return;
|
|
5216
4877
|
}
|
|
5217
4878
|
const metadataPath = join5(outputDir, "metadata.json");
|
|
5218
4879
|
const metadata = await readJsonFile(metadataPath);
|
|
5219
4880
|
if (metadata == null) {
|
|
5220
|
-
|
|
4881
|
+
log9.error("Not a vidistill output directory");
|
|
5221
4882
|
return;
|
|
5222
4883
|
}
|
|
5223
4884
|
const speakerMapping = { ...metadata.speakerMapping ?? {} };
|
|
@@ -5238,18 +4899,18 @@ async function runRename(outputDir, oldName, newName) {
|
|
|
5238
4899
|
const currentNames = Object.values(speakerMapping);
|
|
5239
4900
|
const unmappedLabels = speakers.filter((s) => speakerMapping[s.label] == null).map((s) => s.label);
|
|
5240
4901
|
const allNames = [.../* @__PURE__ */ new Set([...currentNames, ...unmappedLabels])];
|
|
5241
|
-
|
|
4902
|
+
log9.error(`No speaker named "${oldName}" found. Current speakers: ${formatNameList(allNames)}`);
|
|
5242
4903
|
return;
|
|
5243
4904
|
}
|
|
5244
4905
|
if (matchingKeys.length > 1) {
|
|
5245
|
-
|
|
4906
|
+
log9.error(
|
|
5246
4907
|
`Multiple speakers named "${oldName}" (${matchingKeys.join(", ")}). Use SPEAKER_XX label to specify which one.`
|
|
5247
4908
|
);
|
|
5248
4909
|
return;
|
|
5249
4910
|
}
|
|
5250
4911
|
const key = matchingKeys[0];
|
|
5251
4912
|
speakerMapping[key] = newName;
|
|
5252
|
-
|
|
4913
|
+
log9.info("Re-rendering output files with updated speaker names...");
|
|
5253
4914
|
const result = await reRenderWithSpeakerMapping({
|
|
5254
4915
|
outputDir,
|
|
5255
4916
|
speakerMapping,
|
|
@@ -5257,16 +4918,16 @@ async function runRename(outputDir, oldName, newName) {
|
|
|
5257
4918
|
});
|
|
5258
4919
|
if (result.errors.length > 0) {
|
|
5259
4920
|
for (const err of result.errors) {
|
|
5260
|
-
|
|
4921
|
+
log9.error(err);
|
|
5261
4922
|
}
|
|
5262
4923
|
}
|
|
5263
|
-
|
|
4924
|
+
log9.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
|
|
5264
4925
|
}
|
|
5265
4926
|
async function runMerge(outputDir, sourceName, targetName) {
|
|
5266
4927
|
const metadataPath = join5(outputDir, "metadata.json");
|
|
5267
4928
|
const metadata = await readJsonFile(metadataPath);
|
|
5268
4929
|
if (metadata == null) {
|
|
5269
|
-
|
|
4930
|
+
log9.error("Not a vidistill output directory");
|
|
5270
4931
|
return;
|
|
5271
4932
|
}
|
|
5272
4933
|
const speakerMapping = { ...metadata.speakerMapping ?? {} };
|
|
@@ -5295,19 +4956,19 @@ async function runMerge(outputDir, sourceName, targetName) {
|
|
|
5295
4956
|
const targetKeys = findKeys(targetName);
|
|
5296
4957
|
if (sourceKeys.length === 0) {
|
|
5297
4958
|
const currentNames = buildCurrentNames(speakers, speakerMapping);
|
|
5298
|
-
|
|
4959
|
+
log9.error(`No speaker named "${sourceName}" found. Current speakers: ${formatNameList(currentNames)}`);
|
|
5299
4960
|
return;
|
|
5300
4961
|
}
|
|
5301
4962
|
if (targetKeys.length === 0) {
|
|
5302
4963
|
const currentNames = buildCurrentNames(speakers, speakerMapping);
|
|
5303
|
-
|
|
4964
|
+
log9.error(`No speaker named "${targetName}" found. Current speakers: ${formatNameList(currentNames)}`);
|
|
5304
4965
|
return;
|
|
5305
4966
|
}
|
|
5306
4967
|
const resolvedTargetName = speakerMapping[targetKeys[0]] ?? targetName;
|
|
5307
4968
|
for (const key of sourceKeys) {
|
|
5308
4969
|
speakerMapping[key] = resolvedTargetName;
|
|
5309
4970
|
}
|
|
5310
|
-
|
|
4971
|
+
log9.info("Re-rendering output files with updated speaker names...");
|
|
5311
4972
|
const result = await reRenderWithSpeakerMapping({
|
|
5312
4973
|
outputDir,
|
|
5313
4974
|
speakerMapping,
|
|
@@ -5315,10 +4976,10 @@ async function runMerge(outputDir, sourceName, targetName) {
|
|
|
5315
4976
|
});
|
|
5316
4977
|
if (result.errors.length > 0) {
|
|
5317
4978
|
for (const err of result.errors) {
|
|
5318
|
-
|
|
4979
|
+
log9.error(err);
|
|
5319
4980
|
}
|
|
5320
4981
|
}
|
|
5321
|
-
|
|
4982
|
+
log9.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
|
|
5322
4983
|
}
|
|
5323
4984
|
function buildCurrentNames(speakers, speakerMapping) {
|
|
5324
4985
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -5335,11 +4996,11 @@ function buildCurrentNames(speakers, speakerMapping) {
|
|
|
5335
4996
|
async function run2(args) {
|
|
5336
4997
|
const { outputDir, list, rename, merge, error } = parseArgs(args);
|
|
5337
4998
|
if (error != null) {
|
|
5338
|
-
|
|
4999
|
+
log9.error(error);
|
|
5339
5000
|
return;
|
|
5340
5001
|
}
|
|
5341
5002
|
if (outputDir == null || outputDir.trim() === "") {
|
|
5342
|
-
|
|
5003
|
+
log9.error('Usage: vidistill rename-speakers <output-dir> [--list] [--rename "old" "new"] [--merge "source" "target"]');
|
|
5343
5004
|
return;
|
|
5344
5005
|
}
|
|
5345
5006
|
if (list) {
|
|
@@ -5357,22 +5018,22 @@ async function run2(args) {
|
|
|
5357
5018
|
const metadataPath = join5(outputDir, "metadata.json");
|
|
5358
5019
|
const metadata = await readJsonFile(metadataPath);
|
|
5359
5020
|
if (metadata == null) {
|
|
5360
|
-
|
|
5021
|
+
log9.error("Not a vidistill output directory");
|
|
5361
5022
|
return;
|
|
5362
5023
|
}
|
|
5363
5024
|
const rawDir = join5(outputDir, "raw");
|
|
5364
5025
|
const peopleExtraction = await readJsonFile(join5(rawDir, "pass3b-people.json"));
|
|
5365
5026
|
if (peopleExtraction == null) {
|
|
5366
|
-
|
|
5027
|
+
log9.info("No speakers detected in this video");
|
|
5367
5028
|
return;
|
|
5368
5029
|
}
|
|
5369
5030
|
const speakers = await collectSpeakersFromRaw(rawDir);
|
|
5370
5031
|
if (speakers.length === 0) {
|
|
5371
|
-
|
|
5032
|
+
log9.info("No speakers detected in this video");
|
|
5372
5033
|
return;
|
|
5373
5034
|
}
|
|
5374
5035
|
const existingMapping = metadata.speakerMapping ?? {};
|
|
5375
|
-
|
|
5036
|
+
log9.info(
|
|
5376
5037
|
`${String(speakers.length)} speaker${speakers.length === 1 ? "" : "s"} found. Enter names (or press Enter to keep current).`
|
|
5377
5038
|
);
|
|
5378
5039
|
const groups = groupSpeakersByExistingMapping(speakers, existingMapping);
|
|
@@ -5401,7 +5062,7 @@ async function run2(args) {
|
|
|
5401
5062
|
placeholder: "Enter name or press Enter to keep current",
|
|
5402
5063
|
defaultValue
|
|
5403
5064
|
});
|
|
5404
|
-
if (
|
|
5065
|
+
if (isCancel3(value) || typeof value !== "string") {
|
|
5405
5066
|
cancel4("Speaker naming cancelled.");
|
|
5406
5067
|
return;
|
|
5407
5068
|
}
|
|
@@ -5419,7 +5080,7 @@ async function run2(args) {
|
|
|
5419
5080
|
return;
|
|
5420
5081
|
}
|
|
5421
5082
|
const { mapping: finalMapping, declinedMerges } = mergeResult;
|
|
5422
|
-
|
|
5083
|
+
log9.info("Re-rendering output files with updated speaker names...");
|
|
5423
5084
|
const result = await reRenderWithSpeakerMapping({
|
|
5424
5085
|
outputDir,
|
|
5425
5086
|
speakerMapping: finalMapping,
|
|
@@ -5427,14 +5088,14 @@ async function run2(args) {
|
|
|
5427
5088
|
});
|
|
5428
5089
|
if (result.errors.length > 0) {
|
|
5429
5090
|
for (const err of result.errors) {
|
|
5430
|
-
|
|
5091
|
+
log9.error(err);
|
|
5431
5092
|
}
|
|
5432
5093
|
}
|
|
5433
|
-
|
|
5094
|
+
log9.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
|
|
5434
5095
|
}
|
|
5435
5096
|
|
|
5436
5097
|
// src/cli/index.ts
|
|
5437
|
-
var version = "0.4.
|
|
5098
|
+
var version = "0.4.3";
|
|
5438
5099
|
var DEFAULT_OUTPUT = "./vidistill-output/";
|
|
5439
5100
|
var SUBCOMMANDS = {
|
|
5440
5101
|
mcp: run,
|
|
@@ -5487,11 +5148,11 @@ Commands: ${Object.keys(SUBCOMMANDS).join(", ")}`
|
|
|
5487
5148
|
lang: args.lang
|
|
5488
5149
|
});
|
|
5489
5150
|
} catch (err) {
|
|
5490
|
-
const { log:
|
|
5151
|
+
const { log: log10 } = await import("@clack/prompts");
|
|
5491
5152
|
const { default: pc4 } = await import("picocolors");
|
|
5492
5153
|
const raw = err instanceof Error ? err.message : String(err);
|
|
5493
5154
|
const message = raw.split("\n")[0].slice(0, 200);
|
|
5494
|
-
|
|
5155
|
+
log10.error(pc4.red(message));
|
|
5495
5156
|
process.exit(1);
|
|
5496
5157
|
}
|
|
5497
5158
|
}
|
package/package.json
CHANGED