rollberry 0.1.9 → 0.2.0

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.
@@ -0,0 +1,103 @@
1
+ import type { VideoProbeOutcome } from './capture/ffmpeg.js';
2
+ import type { CaptureJob, CaptureResult, CaptureScene, FinalVideoEncodingSettings, IntermediateArtifactProfile, NonEmptyArray, PageCaptureResult } from './capture/types.js';
3
+ import type { ResolvedRenderOutput } from './project.js';
4
+ export interface PlannedSceneCapture {
5
+ sceneIndex: number;
6
+ scene: CaptureScene;
7
+ clipPath: string;
8
+ debugFramesDir?: string;
9
+ job: CaptureJob;
10
+ }
11
+ export interface PlannedComposition {
12
+ outPath: string;
13
+ format: ResolvedRenderOutput['format'];
14
+ fps: number;
15
+ audio: ResolvedRenderOutput['audio'];
16
+ subtitles: ResolvedRenderOutput['subtitles'];
17
+ transition: ResolvedRenderOutput['transition'];
18
+ finalVideo: FinalVideoEncodingSettings;
19
+ force: boolean;
20
+ }
21
+ export interface CompositionCapabilities {
22
+ requiresPreciseClipTiming: boolean;
23
+ timingRequirementSources: string[];
24
+ }
25
+ export interface RenderPlan {
26
+ output: ResolvedRenderOutput;
27
+ tempDir: string;
28
+ sceneCaptures: NonEmptyArray<PlannedSceneCapture>;
29
+ composition: PlannedComposition;
30
+ capabilities: CompositionCapabilities;
31
+ }
32
+ export interface SceneCaptureArtifact {
33
+ captureMetrics: CaptureResult;
34
+ clip: {
35
+ path: string;
36
+ durationSeconds: number;
37
+ frameCount?: number;
38
+ };
39
+ probe: VideoProbeOutcome;
40
+ }
41
+ export interface RenderCaptureMetrics {
42
+ frameCount: number;
43
+ durationSeconds: number;
44
+ truncated: boolean;
45
+ scenes: PageCaptureResult[];
46
+ }
47
+ export interface RenderArtifactMetrics {
48
+ videoPath: string;
49
+ frameCount: number;
50
+ durationSeconds: number;
51
+ truncated: boolean;
52
+ probe: {
53
+ status: VideoProbeOutcome['status'];
54
+ source: 'ffprobe' | 'estimate';
55
+ warning?: string;
56
+ };
57
+ }
58
+ export declare function createRenderPlan(input: {
59
+ output: ResolvedRenderOutput;
60
+ scenes: NonEmptyArray<CaptureScene>;
61
+ timeoutMs: number;
62
+ tempDir: string;
63
+ }): RenderPlan;
64
+ export declare function buildSceneCaptureArtifact(input: {
65
+ rawCapture: CaptureResult;
66
+ clipPath: string;
67
+ probe: VideoProbeOutcome;
68
+ }): SceneCaptureArtifact;
69
+ export declare function buildCaptureMetrics(sceneArtifacts: SceneCaptureArtifact[]): RenderCaptureMetrics;
70
+ export declare function buildArtifactMetrics(input: {
71
+ captureResult: CaptureResult;
72
+ probe: VideoProbeOutcome;
73
+ }): RenderArtifactMetrics;
74
+ export declare function collectProbeWarnings(sceneArtifacts: SceneCaptureArtifact[], finalProbe: VideoProbeOutcome): string[];
75
+ export declare function shouldFailOnSceneClipProbe(input: {
76
+ probe: VideoProbeOutcome;
77
+ capabilities: CompositionCapabilities;
78
+ }): boolean;
79
+ export declare function buildSceneClipProbeFailureMessage(input: {
80
+ sceneIndex: number;
81
+ probe: VideoProbeOutcome;
82
+ capabilities: CompositionCapabilities;
83
+ }): string;
84
+ export declare function buildProbeWarningEvent(input: {
85
+ target: 'scene-clip' | 'output';
86
+ index?: number;
87
+ probe: VideoProbeOutcome;
88
+ }): {
89
+ event: string;
90
+ message: string;
91
+ metadata: Record<string, unknown>;
92
+ } | null;
93
+ export declare function resolveCompositionCapabilities(input: {
94
+ transition?: ResolvedRenderOutput['transition'];
95
+ intermediateArtifact: IntermediateArtifactProfile;
96
+ }): CompositionCapabilities;
97
+ export declare function buildComposedCaptureResult(input: {
98
+ outPath: string;
99
+ fps: number;
100
+ sceneArtifacts: SceneCaptureArtifact[];
101
+ transition?: ResolvedRenderOutput['transition'];
102
+ probe: VideoProbeOutcome;
103
+ }): CaptureResult;
@@ -0,0 +1,144 @@
1
+ import { join } from 'node:path';
2
+ export function createRenderPlan(input) {
3
+ const { output, scenes, timeoutMs, tempDir } = input;
4
+ const sceneCaptures = scenes.map((scene, sceneIndex) => {
5
+ const clipPath = join(tempDir, `scene-${String(sceneIndex + 1).padStart(3, '0')}${output.intermediateArtifact.extension}`);
6
+ const debugFramesDir = output.debugFramesDir
7
+ ? join(output.debugFramesDir, `scene-${String(sceneIndex + 1).padStart(3, '0')}`)
8
+ : undefined;
9
+ return {
10
+ sceneIndex,
11
+ scene,
12
+ clipPath,
13
+ debugFramesDir,
14
+ job: {
15
+ scenes: [scene],
16
+ outPath: clipPath,
17
+ format: output.intermediateArtifact.format,
18
+ viewport: output.viewport,
19
+ fps: output.fps,
20
+ timeoutMs,
21
+ debugFramesDir,
22
+ videoEncoding: output.intermediateArtifact.videoEncoding,
23
+ includeHoldAfterFinalScene: sceneIndex < scenes.length - 1,
24
+ force: true,
25
+ },
26
+ };
27
+ });
28
+ return {
29
+ output,
30
+ tempDir,
31
+ sceneCaptures,
32
+ composition: {
33
+ outPath: output.outPath,
34
+ format: output.format,
35
+ fps: output.fps,
36
+ audio: output.audio,
37
+ subtitles: output.subtitles,
38
+ transition: output.transition,
39
+ finalVideo: output.finalVideo,
40
+ force: output.force,
41
+ },
42
+ capabilities: resolveCompositionCapabilities({
43
+ transition: output.transition,
44
+ intermediateArtifact: output.intermediateArtifact,
45
+ }),
46
+ };
47
+ }
48
+ export function buildSceneCaptureArtifact(input) {
49
+ const durationSeconds = input.probe.result?.durationSeconds ?? input.rawCapture.durationSeconds;
50
+ const frameCount = input.probe.result?.frameCount ?? input.rawCapture.frameCount;
51
+ return {
52
+ captureMetrics: input.rawCapture,
53
+ clip: {
54
+ path: input.clipPath,
55
+ durationSeconds,
56
+ frameCount,
57
+ },
58
+ probe: input.probe,
59
+ };
60
+ }
61
+ export function buildCaptureMetrics(sceneArtifacts) {
62
+ return {
63
+ frameCount: sceneArtifacts.reduce((sum, artifact) => sum + artifact.captureMetrics.frameCount, 0),
64
+ durationSeconds: sceneArtifacts.reduce((sum, artifact) => sum + artifact.captureMetrics.durationSeconds, 0),
65
+ truncated: sceneArtifacts.some((artifact) => artifact.captureMetrics.truncated),
66
+ scenes: sceneArtifacts.flatMap((artifact) => artifact.captureMetrics.pages),
67
+ };
68
+ }
69
+ export function buildArtifactMetrics(input) {
70
+ return {
71
+ videoPath: input.captureResult.outPath,
72
+ frameCount: input.captureResult.frameCount,
73
+ durationSeconds: input.captureResult.durationSeconds,
74
+ truncated: input.captureResult.truncated,
75
+ probe: {
76
+ status: input.probe.status,
77
+ source: input.probe.status === 'probed' ? 'ffprobe' : 'estimate',
78
+ warning: input.probe.warning,
79
+ },
80
+ };
81
+ }
82
+ export function collectProbeWarnings(sceneArtifacts, finalProbe) {
83
+ const warnings = new Set();
84
+ for (const artifact of sceneArtifacts) {
85
+ if (artifact.probe.status !== 'probed') {
86
+ warnings.add(`scene_clip_probe_${artifact.probe.status}`);
87
+ }
88
+ }
89
+ if (finalProbe.status !== 'probed') {
90
+ warnings.add(`output_probe_${finalProbe.status}`);
91
+ }
92
+ return [...warnings];
93
+ }
94
+ export function shouldFailOnSceneClipProbe(input) {
95
+ return (input.probe.status !== 'probed' &&
96
+ input.capabilities.requiresPreciseClipTiming);
97
+ }
98
+ export function buildSceneClipProbeFailureMessage(input) {
99
+ return `Scene clip probe failed for scene ${input.sceneIndex + 1} (${input.probe.status}). Precise clip timing is required by: ${input.capabilities.timingRequirementSources.join(', ')}. ${input.probe.warning ?? ''}`.trim();
100
+ }
101
+ export function buildProbeWarningEvent(input) {
102
+ if (input.probe.status === 'probed') {
103
+ return null;
104
+ }
105
+ return {
106
+ event: 'probe.warning',
107
+ message: input.target === 'scene-clip'
108
+ ? 'Scene clip probe warning'
109
+ : 'Output probe warning',
110
+ metadata: {
111
+ target: input.target,
112
+ index: input.index,
113
+ status: input.probe.status,
114
+ warning: input.probe.warning,
115
+ },
116
+ };
117
+ }
118
+ export function resolveCompositionCapabilities(input) {
119
+ const timingRequirementSources = [];
120
+ if (input.transition?.kind === 'crossfade') {
121
+ timingRequirementSources.push('transition:crossfade');
122
+ }
123
+ return {
124
+ requiresPreciseClipTiming: timingRequirementSources.length > 0,
125
+ timingRequirementSources,
126
+ };
127
+ }
128
+ export function buildComposedCaptureResult(input) {
129
+ const pages = input.sceneArtifacts.flatMap((artifact) => artifact.captureMetrics.pages);
130
+ const totalClipDurationSeconds = input.sceneArtifacts.reduce((sum, artifact) => sum + artifact.clip.durationSeconds, 0);
131
+ const transitionOverlapSeconds = input.transition?.kind === 'crossfade'
132
+ ? input.transition.durationSeconds * Math.max(0, pages.length - 1)
133
+ : 0;
134
+ const estimatedDurationSeconds = Math.max(0, totalClipDurationSeconds - transitionOverlapSeconds);
135
+ const durationSeconds = input.probe.result?.durationSeconds ?? estimatedDurationSeconds;
136
+ return {
137
+ outPath: input.outPath,
138
+ frameCount: input.probe.result?.frameCount ??
139
+ Math.max(1, Math.round(durationSeconds * input.fps)),
140
+ durationSeconds,
141
+ pages,
142
+ truncated: input.sceneArtifacts.some((artifact) => artifact.captureMetrics.truncated),
143
+ };
144
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProgressReporter } from './capture/progress.js';
2
+ import type { CaptureOptions, CaptureRunResult } from './capture/types.js';
3
+ export declare function runCaptureCommand(options: CaptureOptions, progress?: ProgressReporter, signal?: AbortSignal): Promise<CaptureRunResult>;
@@ -0,0 +1,17 @@
1
+ import type { ProgressReporter } from './capture/progress.js';
2
+ import type { CaptureResult } from './capture/types.js';
3
+ import type { RenderCliOptions } from './options.js';
4
+ export interface RenderOutputResult {
5
+ name: string;
6
+ fps: number;
7
+ format: 'mp4' | 'webm';
8
+ capture: CaptureResult;
9
+ manifestPath: string;
10
+ logFilePath: string;
11
+ }
12
+ export interface RenderCommandResult {
13
+ projectPath: string;
14
+ summaryManifestPath: string;
15
+ outputs: RenderOutputResult[];
16
+ }
17
+ export declare function runRenderCommand(options: RenderCliOptions, progress?: ProgressReporter, signal?: AbortSignal): Promise<RenderCommandResult>;