vidpipe 1.3.8 → 1.3.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 +62 -0
- package/dist/cli.js +670 -40
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +816 -0
- package/dist/index.js +12049 -0
- package/dist/index.js.map +1 -0
- package/package.json +13 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for vidpipe CLI pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Domain types covering transcription, video metadata, short-clip planning,
|
|
5
|
+
* social-media post generation, and end-to-end pipeline orchestration.
|
|
6
|
+
*
|
|
7
|
+
* ### Timestamp convention
|
|
8
|
+
* All `start` and `end` fields are in **seconds from the beginning of the video**
|
|
9
|
+
* (floating-point, e.g. 12.345). This matches Whisper's output format and
|
|
10
|
+
* FFmpeg's `-ss` / `-to` parameters.
|
|
11
|
+
*/
|
|
12
|
+
/** Social-media platforms supported for post generation. */
|
|
13
|
+
declare enum Platform {
|
|
14
|
+
TikTok = "tiktok",
|
|
15
|
+
YouTube = "youtube",
|
|
16
|
+
Instagram = "instagram",
|
|
17
|
+
LinkedIn = "linkedin",
|
|
18
|
+
X = "x"
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A single word with precise start/end timestamps from Whisper.
|
|
22
|
+
*
|
|
23
|
+
* Word-level timestamps are the foundation of the karaoke caption system —
|
|
24
|
+
* each word knows exactly when it's spoken, enabling per-word highlighting.
|
|
25
|
+
* Whisper produces these via its `--word_timestamps` flag.
|
|
26
|
+
*
|
|
27
|
+
* @property word - The spoken word (may include leading/trailing whitespace)
|
|
28
|
+
* @property start - When this word begins, in seconds from video start
|
|
29
|
+
* @property end - When this word ends, in seconds from video start
|
|
30
|
+
*/
|
|
31
|
+
interface Word {
|
|
32
|
+
word: string;
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A sentence/phrase-level segment from Whisper transcription.
|
|
38
|
+
*
|
|
39
|
+
* Segments are Whisper's natural grouping of words into sentences or clauses.
|
|
40
|
+
* They're used for SRT/VTT subtitle generation (one cue per segment) and for
|
|
41
|
+
* silence removal (segments that fall entirely within a removed region are dropped).
|
|
42
|
+
*
|
|
43
|
+
* @property id - Sequential segment index (0-based)
|
|
44
|
+
* @property text - Full text of the segment
|
|
45
|
+
* @property start - Segment start time in seconds
|
|
46
|
+
* @property end - Segment end time in seconds
|
|
47
|
+
* @property words - The individual words with their own timestamps
|
|
48
|
+
*/
|
|
49
|
+
interface Segment {
|
|
50
|
+
id: number;
|
|
51
|
+
text: string;
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
words: Word[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Complete transcript result from Whisper.
|
|
58
|
+
*
|
|
59
|
+
* Contains both segment-level and word-level data. The top-level `words` array
|
|
60
|
+
* is a flat list of all words across all segments — this is the primary input
|
|
61
|
+
* for the ASS caption generator's karaoke highlighting.
|
|
62
|
+
*
|
|
63
|
+
* @property text - Full transcript as a single string
|
|
64
|
+
* @property segments - Sentence/phrase-level segments
|
|
65
|
+
* @property words - Flat array of all words with timestamps (used by ASS captions)
|
|
66
|
+
* @property language - Detected language code (e.g. "en")
|
|
67
|
+
* @property duration - Total video duration in seconds
|
|
68
|
+
*/
|
|
69
|
+
interface Transcript {
|
|
70
|
+
text: string;
|
|
71
|
+
segments: Segment[];
|
|
72
|
+
words: Word[];
|
|
73
|
+
language: string;
|
|
74
|
+
duration: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Metadata for a video file after ingestion into the repo structure.
|
|
78
|
+
*
|
|
79
|
+
* @property originalPath - Where the file was picked up from (e.g. recordings/ folder)
|
|
80
|
+
* @property repoPath - Canonical path within the repo's asset directory
|
|
81
|
+
* @property videoDir - Directory containing all generated assets for this video
|
|
82
|
+
* @property slug - URL/filesystem-safe name derived from the filename (e.g. "my-video-2024-01-15")
|
|
83
|
+
* @property filename - Original filename with extension
|
|
84
|
+
* @property duration - Video duration in seconds (from ffprobe)
|
|
85
|
+
* @property size - File size in bytes
|
|
86
|
+
* @property createdAt - File creation timestamp
|
|
87
|
+
* @property layout - Detected layout metadata (webcam region, etc.)
|
|
88
|
+
*/
|
|
89
|
+
interface VideoFile {
|
|
90
|
+
originalPath: string;
|
|
91
|
+
repoPath: string;
|
|
92
|
+
videoDir: string;
|
|
93
|
+
slug: string;
|
|
94
|
+
filename: string;
|
|
95
|
+
duration: number;
|
|
96
|
+
size: number;
|
|
97
|
+
createdAt: Date;
|
|
98
|
+
layout?: VideoLayout;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Detected layout metadata for a video.
|
|
102
|
+
* Captures webcam and screen region positions for use in aspect ratio conversion
|
|
103
|
+
* and production effects.
|
|
104
|
+
*/
|
|
105
|
+
interface VideoLayout {
|
|
106
|
+
/** Video dimensions */
|
|
107
|
+
width: number;
|
|
108
|
+
height: number;
|
|
109
|
+
/** Detected webcam overlay region (null if no webcam detected) */
|
|
110
|
+
webcam: WebcamRegion | null;
|
|
111
|
+
/** Main screen content region (computed as inverse of webcam) */
|
|
112
|
+
screen: ScreenRegion | null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Webcam overlay region in a screen recording.
|
|
116
|
+
*/
|
|
117
|
+
interface WebcamRegion {
|
|
118
|
+
x: number;
|
|
119
|
+
y: number;
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
123
|
+
confidence: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Main screen content region (area not occupied by webcam).
|
|
127
|
+
*/
|
|
128
|
+
interface ScreenRegion {
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
width: number;
|
|
132
|
+
height: number;
|
|
133
|
+
}
|
|
134
|
+
type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5';
|
|
135
|
+
type VideoPlatform = 'tiktok' | 'youtube-shorts' | 'instagram-reels' | 'instagram-feed' | 'linkedin' | 'youtube' | 'twitter';
|
|
136
|
+
/**
|
|
137
|
+
* Caption rendering style.
|
|
138
|
+
* - `'shorts'` — large centered pop captions for short-form clips (landscape 16:9)
|
|
139
|
+
* - `'medium'` — smaller bottom-positioned captions for longer content
|
|
140
|
+
* - `'portrait'` — Opus Clips style for 9:16 vertical video (green highlight,
|
|
141
|
+
* scale-pop animation, larger fonts for small-screen viewing)
|
|
142
|
+
*/
|
|
143
|
+
type CaptionStyle = 'shorts' | 'medium' | 'portrait';
|
|
144
|
+
interface ShortClipVariant {
|
|
145
|
+
path: string;
|
|
146
|
+
aspectRatio: AspectRatio;
|
|
147
|
+
platform: VideoPlatform;
|
|
148
|
+
width: number;
|
|
149
|
+
height: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A single time range within a short clip.
|
|
153
|
+
*
|
|
154
|
+
* Short clips can be **composite** — made of multiple non-contiguous segments
|
|
155
|
+
* from the original video, concatenated together. Each segment describes one
|
|
156
|
+
* contiguous range.
|
|
157
|
+
*
|
|
158
|
+
* @property start - Start time in the original video (seconds)
|
|
159
|
+
* @property end - End time in the original video (seconds)
|
|
160
|
+
* @property description - Human-readable description of what happens in this segment
|
|
161
|
+
*/
|
|
162
|
+
interface ShortSegment {
|
|
163
|
+
start: number;
|
|
164
|
+
end: number;
|
|
165
|
+
description: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A planned short clip (15–60s) extracted from the full video.
|
|
169
|
+
*
|
|
170
|
+
* May be a single contiguous segment or a **composite** of multiple segments
|
|
171
|
+
* concatenated together (e.g. an intro + punchline from different parts of
|
|
172
|
+
* the video). The `segments` array defines the source time ranges; `totalDuration`
|
|
173
|
+
* is the sum of all segment durations.
|
|
174
|
+
*
|
|
175
|
+
* @property id - Unique identifier (e.g. "short-1")
|
|
176
|
+
* @property title - Human-readable title for the clip
|
|
177
|
+
* @property slug - Filesystem-safe slug (e.g. "typescript-tip-generics")
|
|
178
|
+
* @property segments - One or more time ranges from the original video
|
|
179
|
+
* @property totalDuration - Sum of all segment durations in seconds
|
|
180
|
+
* @property outputPath - Path to the extracted video file
|
|
181
|
+
* @property captionedPath - Path to the captioned version (if generated)
|
|
182
|
+
* @property description - Short description for social media
|
|
183
|
+
* @property tags - Hashtags / topic tags
|
|
184
|
+
* @property variants - Platform-specific aspect-ratio variants (portrait, square, etc.)
|
|
185
|
+
*/
|
|
186
|
+
/** Hook pattern used to capture viewer attention in the first 1-3 seconds */
|
|
187
|
+
type HookType = 'cold-open' | 'curiosity-gap' | 'contradiction' | 'result-first' | 'bold-claim' | 'question';
|
|
188
|
+
/** Primary emotional trigger that drives engagement (shares, saves, comments) */
|
|
189
|
+
type EmotionalTrigger = 'awe' | 'humor' | 'surprise' | 'empathy' | 'outrage' | 'practical-value';
|
|
190
|
+
/** Narrative arc structure used in short clips */
|
|
191
|
+
type ShortNarrativeStructure = 'result-method-proof' | 'doing-x-wrong' | 'expectation-vs-reality' | 'mini-list' | 'tension-release' | 'loop';
|
|
192
|
+
interface ShortClip {
|
|
193
|
+
id: string;
|
|
194
|
+
title: string;
|
|
195
|
+
slug: string;
|
|
196
|
+
segments: ShortSegment[];
|
|
197
|
+
totalDuration: number;
|
|
198
|
+
outputPath: string;
|
|
199
|
+
captionedPath?: string;
|
|
200
|
+
description: string;
|
|
201
|
+
tags: string[];
|
|
202
|
+
hook?: string;
|
|
203
|
+
variants?: ShortClipVariant[];
|
|
204
|
+
/** Hook pattern classification — how the opening captures attention */
|
|
205
|
+
hookType?: HookType;
|
|
206
|
+
/** Primary emotional driver that makes this clip engaging */
|
|
207
|
+
emotionalTrigger?: EmotionalTrigger;
|
|
208
|
+
/** Viral potential score (1-20) based on hook strength, emotion, shareability, completion, replay */
|
|
209
|
+
viralScore?: number;
|
|
210
|
+
/** Narrative arc pattern used in this clip */
|
|
211
|
+
narrativeStructure?: ShortNarrativeStructure;
|
|
212
|
+
/** Why would someone share this with a friend? */
|
|
213
|
+
shareReason?: string;
|
|
214
|
+
/** Whether the content naturally loops back to the beginning */
|
|
215
|
+
isLoopCandidate?: boolean;
|
|
216
|
+
}
|
|
217
|
+
/** A planned medium clip segment */
|
|
218
|
+
interface MediumSegment {
|
|
219
|
+
start: number;
|
|
220
|
+
end: number;
|
|
221
|
+
description: string;
|
|
222
|
+
}
|
|
223
|
+
/** Narrative arc structure used in medium clips */
|
|
224
|
+
type MediumNarrativeStructure = 'open-loop-steps-payoff' | 'problem-deepdive-solution' | 'story-arc' | 'debate-comparison' | 'tutorial-micropayoffs';
|
|
225
|
+
/** Classification of medium clip content type */
|
|
226
|
+
type MediumClipType = 'deep-dive' | 'tutorial' | 'story-arc' | 'debate' | 'problem-solution';
|
|
227
|
+
interface MediumClip {
|
|
228
|
+
id: string;
|
|
229
|
+
title: string;
|
|
230
|
+
slug: string;
|
|
231
|
+
segments: MediumSegment[];
|
|
232
|
+
totalDuration: number;
|
|
233
|
+
outputPath: string;
|
|
234
|
+
captionedPath?: string;
|
|
235
|
+
description: string;
|
|
236
|
+
tags: string[];
|
|
237
|
+
hook: string;
|
|
238
|
+
topic: string;
|
|
239
|
+
/** Hook pattern classification — how the opening captures attention */
|
|
240
|
+
hookType?: HookType;
|
|
241
|
+
/** Primary emotional driver that makes this clip engaging */
|
|
242
|
+
emotionalTrigger?: EmotionalTrigger;
|
|
243
|
+
/** Viral potential score (1-20) based on hook strength, emotion, shareability, completion, replay */
|
|
244
|
+
viralScore?: number;
|
|
245
|
+
/** Narrative arc pattern used in this clip */
|
|
246
|
+
narrativeStructure?: MediumNarrativeStructure;
|
|
247
|
+
/** Content type classification */
|
|
248
|
+
clipType?: MediumClipType;
|
|
249
|
+
/** Why would someone save this to reference later? */
|
|
250
|
+
saveReason?: string;
|
|
251
|
+
/** Retention hooks planned at ~15-20 second intervals within the clip */
|
|
252
|
+
microHooks?: string[];
|
|
253
|
+
}
|
|
254
|
+
interface SocialPost {
|
|
255
|
+
platform: Platform;
|
|
256
|
+
content: string;
|
|
257
|
+
hashtags: string[];
|
|
258
|
+
links: string[];
|
|
259
|
+
characterCount: number;
|
|
260
|
+
outputPath: string;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* A chapter marker for YouTube's chapters feature.
|
|
264
|
+
*
|
|
265
|
+
* @property timestamp - Start time in seconds (YouTube shows these as clickable markers)
|
|
266
|
+
* @property title - Short chapter title (shown in the progress bar)
|
|
267
|
+
* @property description - Longer description for the README/summary
|
|
268
|
+
*/
|
|
269
|
+
interface Chapter {
|
|
270
|
+
timestamp: number;
|
|
271
|
+
title: string;
|
|
272
|
+
description: string;
|
|
273
|
+
}
|
|
274
|
+
interface VideoSnapshot {
|
|
275
|
+
timestamp: number;
|
|
276
|
+
description: string;
|
|
277
|
+
outputPath: string;
|
|
278
|
+
}
|
|
279
|
+
interface VideoSummary {
|
|
280
|
+
title: string;
|
|
281
|
+
overview: string;
|
|
282
|
+
keyTopics: string[];
|
|
283
|
+
snapshots: VideoSnapshot[];
|
|
284
|
+
markdownPath: string;
|
|
285
|
+
}
|
|
286
|
+
/** Placement region for an image overlay on video */
|
|
287
|
+
type OverlayRegion = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center-right' | 'center-left';
|
|
288
|
+
/** Where on screen to place an overlay image */
|
|
289
|
+
interface OverlayPlacement {
|
|
290
|
+
region: OverlayRegion;
|
|
291
|
+
avoidAreas: string[];
|
|
292
|
+
sizePercent: number;
|
|
293
|
+
}
|
|
294
|
+
/** A moment in the video identified by Gemini as needing a visual aid */
|
|
295
|
+
interface EnhancementOpportunity {
|
|
296
|
+
timestampStart: number;
|
|
297
|
+
timestampEnd: number;
|
|
298
|
+
topic: string;
|
|
299
|
+
imagePrompt: string;
|
|
300
|
+
reason: string;
|
|
301
|
+
placement: OverlayPlacement;
|
|
302
|
+
confidence: number;
|
|
303
|
+
}
|
|
304
|
+
/** A generated image overlay ready for FFmpeg compositing */
|
|
305
|
+
interface GeneratedOverlay {
|
|
306
|
+
opportunity: EnhancementOpportunity;
|
|
307
|
+
imagePath: string;
|
|
308
|
+
width: number;
|
|
309
|
+
height: number;
|
|
310
|
+
}
|
|
311
|
+
/** Result of the visual enhancement stage */
|
|
312
|
+
interface VisualEnhancementResult {
|
|
313
|
+
enhancedVideoPath: string;
|
|
314
|
+
overlays: GeneratedOverlay[];
|
|
315
|
+
analysisTokens: number;
|
|
316
|
+
imageGenCost: number;
|
|
317
|
+
}
|
|
318
|
+
declare enum PipelineStage {
|
|
319
|
+
Ingestion = "ingestion",
|
|
320
|
+
Transcription = "transcription",
|
|
321
|
+
SilenceRemoval = "silence-removal",
|
|
322
|
+
VisualEnhancement = "visual-enhancement",
|
|
323
|
+
Chapters = "chapters",
|
|
324
|
+
Captions = "captions",
|
|
325
|
+
CaptionBurn = "caption-burn",
|
|
326
|
+
Summary = "summary",
|
|
327
|
+
Shorts = "shorts",
|
|
328
|
+
MediumClips = "medium-clips",
|
|
329
|
+
SocialMedia = "social-media",
|
|
330
|
+
ShortPosts = "short-posts",
|
|
331
|
+
MediumClipPosts = "medium-clip-posts",
|
|
332
|
+
Blog = "blog",
|
|
333
|
+
QueueBuild = "queue-build",
|
|
334
|
+
GitPush = "git-push"
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Per-stage outcome record for pipeline observability.
|
|
338
|
+
*
|
|
339
|
+
* @property stage - Which pipeline stage this result is for
|
|
340
|
+
* @property success - Whether the stage completed without throwing
|
|
341
|
+
* @property error - Error message if the stage failed
|
|
342
|
+
* @property duration - Wall-clock time in milliseconds
|
|
343
|
+
*/
|
|
344
|
+
interface StageResult {
|
|
345
|
+
stage: PipelineStage;
|
|
346
|
+
success: boolean;
|
|
347
|
+
error?: string;
|
|
348
|
+
duration: number;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Complete output of a pipeline run.
|
|
352
|
+
*
|
|
353
|
+
* Fields are optional because stages can fail independently — a failed
|
|
354
|
+
* transcription means no summary, but the video metadata is still available.
|
|
355
|
+
*
|
|
356
|
+
* @property totalDuration - Total pipeline wall-clock time in milliseconds
|
|
357
|
+
*/
|
|
358
|
+
interface PipelineResult {
|
|
359
|
+
video: VideoFile;
|
|
360
|
+
transcript?: Transcript;
|
|
361
|
+
editedVideoPath?: string;
|
|
362
|
+
captions?: string[];
|
|
363
|
+
captionedVideoPath?: string;
|
|
364
|
+
enhancedVideoPath?: string;
|
|
365
|
+
summary?: VideoSummary;
|
|
366
|
+
chapters?: Chapter[];
|
|
367
|
+
shorts: ShortClip[];
|
|
368
|
+
mediumClips: MediumClip[];
|
|
369
|
+
socialPosts: SocialPost[];
|
|
370
|
+
blogPost?: string;
|
|
371
|
+
stageResults: StageResult[];
|
|
372
|
+
totalDuration: number;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Result of the silence removal stage.
|
|
376
|
+
*
|
|
377
|
+
* @property editedPath - Path to the video with silence regions cut out
|
|
378
|
+
* @property removals - Time ranges that were removed (in original video time).
|
|
379
|
+
* Used by {@link adjustTranscript} to shift transcript timestamps.
|
|
380
|
+
* @property keepSegments - Inverse of removals — the time ranges that were kept.
|
|
381
|
+
* Used by the single-pass caption burn to re-create the edit from the original.
|
|
382
|
+
* @property wasEdited - False if no silence was found and the video is unchanged
|
|
383
|
+
*/
|
|
384
|
+
interface SilenceRemovalResult {
|
|
385
|
+
editedPath: string;
|
|
386
|
+
removals: {
|
|
387
|
+
start: number;
|
|
388
|
+
end: number;
|
|
389
|
+
}[];
|
|
390
|
+
keepSegments: {
|
|
391
|
+
start: number;
|
|
392
|
+
end: number;
|
|
393
|
+
}[];
|
|
394
|
+
wasEdited: boolean;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Standard result wrapper for all Copilot SDK agent calls.
|
|
398
|
+
*
|
|
399
|
+
* @property success - Whether the agent completed its task
|
|
400
|
+
* @property data - The parsed result (type varies by agent)
|
|
401
|
+
* @property error - Error message if the agent failed
|
|
402
|
+
* @property usage - Token counts for cost tracking
|
|
403
|
+
*/
|
|
404
|
+
interface AgentResult<T = unknown> {
|
|
405
|
+
success: boolean;
|
|
406
|
+
data?: T;
|
|
407
|
+
error?: string;
|
|
408
|
+
usage?: {
|
|
409
|
+
promptTokens: number;
|
|
410
|
+
completionTokens: number;
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/** Character limits per social media platform */
|
|
414
|
+
declare const PLATFORM_CHAR_LIMITS: Record<string, number>;
|
|
415
|
+
/**
|
|
416
|
+
* Maps vidpipe Platform enum values to Late API platform strings.
|
|
417
|
+
* Platform.X = 'x' but Late API expects 'twitter'.
|
|
418
|
+
*/
|
|
419
|
+
declare function toLatePlatform(platform: Platform): string;
|
|
420
|
+
/**
|
|
421
|
+
* Maps a Late API platform string back to vidpipe Platform enum.
|
|
422
|
+
*
|
|
423
|
+
* Validates the input against known Platform values to avoid admitting
|
|
424
|
+
* unknown/unsupported platforms via an unchecked cast.
|
|
425
|
+
*
|
|
426
|
+
* @throws {Error} If the platform is not supported
|
|
427
|
+
*/
|
|
428
|
+
declare function fromLatePlatform(latePlatform: string): Platform;
|
|
429
|
+
/**
|
|
430
|
+
* Normalizes raw platform strings (e.g., from user input or API responses)
|
|
431
|
+
* to Late API platform names. Handles X/Twitter variants and case-insensitivity.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* normalizePlatformString('X') // 'twitter'
|
|
435
|
+
* normalizePlatformString('x (twitter)') // 'twitter'
|
|
436
|
+
* normalizePlatformString('YouTube') // 'youtube'
|
|
437
|
+
*/
|
|
438
|
+
declare function normalizePlatformString(raw: string): string;
|
|
439
|
+
/** Status lifecycle for content ideas */
|
|
440
|
+
type IdeaStatus = 'draft' | 'ready' | 'recorded' | 'published';
|
|
441
|
+
/**
|
|
442
|
+
* Record of a piece of content published from an idea.
|
|
443
|
+
* Appended to `Idea.publishedContent` when queue items are approved/published.
|
|
444
|
+
*/
|
|
445
|
+
interface IdeaPublishRecord {
|
|
446
|
+
/** Content type that was published */
|
|
447
|
+
clipType: 'video' | 'short' | 'medium-clip';
|
|
448
|
+
/** Platform where content was published */
|
|
449
|
+
platform: Platform;
|
|
450
|
+
/** Links back to QueueItemMetadata.id */
|
|
451
|
+
queueItemId: string;
|
|
452
|
+
/** When the content was published (ISO 8601) */
|
|
453
|
+
publishedAt: string;
|
|
454
|
+
/** Late API post ID for tracking/managing the scheduled post */
|
|
455
|
+
latePostId: string;
|
|
456
|
+
/** Late API dashboard URL for viewing the post */
|
|
457
|
+
lateUrl: string;
|
|
458
|
+
/** Final published URL if available */
|
|
459
|
+
publishedUrl?: string;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* A content idea generated by the IdeationAgent or created manually.
|
|
463
|
+
*
|
|
464
|
+
* Ideas flow through the pipeline: they are created during ideation,
|
|
465
|
+
* linked to recordings during processing, and tracked through publishing.
|
|
466
|
+
* The `status` field tracks the lifecycle: draft → ready → recorded → published.
|
|
467
|
+
*/
|
|
468
|
+
interface Idea {
|
|
469
|
+
/** GitHub Issue number — the primary identifier */
|
|
470
|
+
issueNumber: number;
|
|
471
|
+
/** GitHub Issue URL (e.g., https://github.com/htekdev/content-management/issues/1) */
|
|
472
|
+
issueUrl: string;
|
|
473
|
+
/** Repository full name (e.g., htekdev/content-management) */
|
|
474
|
+
repoFullName: string;
|
|
475
|
+
/** Legacy slug ID for migration compatibility (e.g., "idea-copilot-debugging") */
|
|
476
|
+
id: string;
|
|
477
|
+
/** Main topic/title of the idea (= issue title) */
|
|
478
|
+
topic: string;
|
|
479
|
+
/** The attention-grabbing angle (≤80 chars) */
|
|
480
|
+
hook: string;
|
|
481
|
+
/** Who this content is for */
|
|
482
|
+
audience: string;
|
|
483
|
+
/** The one thing the viewer should remember */
|
|
484
|
+
keyTakeaway: string;
|
|
485
|
+
/** Bullet points to cover in the recording */
|
|
486
|
+
talkingPoints: string[];
|
|
487
|
+
/** Target platforms for this content (derived from platform:* labels) */
|
|
488
|
+
platforms: Platform[];
|
|
489
|
+
/** Lifecycle status (derived from status:* label) */
|
|
490
|
+
status: IdeaStatus;
|
|
491
|
+
/** Tags for categorization and matching (derived from freeform labels) */
|
|
492
|
+
tags: string[];
|
|
493
|
+
/** When the idea was created (from issue created_at) */
|
|
494
|
+
createdAt: string;
|
|
495
|
+
/** When the idea was last updated (from issue updated_at) */
|
|
496
|
+
updatedAt: string;
|
|
497
|
+
/** Deadline for publishing this idea's content (ISO 8601 date). Agent sets based on timeliness:
|
|
498
|
+
* - Hot trend: 3-5 days out
|
|
499
|
+
* - Timely event: 1-2 weeks out
|
|
500
|
+
* - Evergreen: 3-6 months out */
|
|
501
|
+
publishBy: string;
|
|
502
|
+
/** Video slug linked after recording — parsed from video-link issue comments */
|
|
503
|
+
sourceVideoSlug?: string;
|
|
504
|
+
/** Why this is timely — context from trend research */
|
|
505
|
+
trendContext?: string;
|
|
506
|
+
/** Tracks every piece of content published for this idea — parsed from issue comments */
|
|
507
|
+
publishedContent?: IdeaPublishRecord[];
|
|
508
|
+
}
|
|
509
|
+
/** Input for creating a new idea — omits fields derived from GitHub Issue metadata */
|
|
510
|
+
interface CreateIdeaInput {
|
|
511
|
+
/** Main topic/title (becomes issue title) */
|
|
512
|
+
topic: string;
|
|
513
|
+
/** The attention-grabbing angle (≤80 chars) */
|
|
514
|
+
hook: string;
|
|
515
|
+
/** Who this content is for */
|
|
516
|
+
audience: string;
|
|
517
|
+
/** The one thing the viewer should remember */
|
|
518
|
+
keyTakeaway: string;
|
|
519
|
+
/** Bullet points to cover in the recording */
|
|
520
|
+
talkingPoints: string[];
|
|
521
|
+
/** Target platforms for this content */
|
|
522
|
+
platforms: Platform[];
|
|
523
|
+
/** Tags for categorization and matching */
|
|
524
|
+
tags: string[];
|
|
525
|
+
/** Deadline for publishing (ISO 8601 date) */
|
|
526
|
+
publishBy: string;
|
|
527
|
+
/** Why this is timely — context from trend research */
|
|
528
|
+
trendContext?: string;
|
|
529
|
+
}
|
|
530
|
+
/** Filters for querying ideas from GitHub Issues */
|
|
531
|
+
interface IdeaFilters {
|
|
532
|
+
/** Filter by lifecycle status */
|
|
533
|
+
status?: IdeaStatus;
|
|
534
|
+
/** Filter by target platform */
|
|
535
|
+
platform?: Platform;
|
|
536
|
+
/** Filter by tag label */
|
|
537
|
+
tag?: string;
|
|
538
|
+
/** Filter by priority label */
|
|
539
|
+
priority?: 'hot-trend' | 'timely' | 'evergreen';
|
|
540
|
+
/** Maximum number of results */
|
|
541
|
+
limit?: number;
|
|
542
|
+
}
|
|
543
|
+
/** Discriminated type for structured issue comments */
|
|
544
|
+
type IdeaCommentData = {
|
|
545
|
+
type: 'publish-record';
|
|
546
|
+
record: IdeaPublishRecord;
|
|
547
|
+
} | {
|
|
548
|
+
type: 'video-link';
|
|
549
|
+
videoSlug: string;
|
|
550
|
+
linkedAt: string;
|
|
551
|
+
};
|
|
552
|
+
/** Schedule time slot for a platform */
|
|
553
|
+
interface ScheduleSlot {
|
|
554
|
+
platform: string;
|
|
555
|
+
scheduledFor: string;
|
|
556
|
+
postId?: string;
|
|
557
|
+
itemId?: string;
|
|
558
|
+
label?: string;
|
|
559
|
+
}
|
|
560
|
+
/** File extensions accepted as pipeline input. */
|
|
561
|
+
declare const SUPPORTED_VIDEO_EXTENSIONS: readonly [".mp4", ".webm"];
|
|
562
|
+
|
|
563
|
+
interface AppEnvironment {
|
|
564
|
+
OPENAI_API_KEY: string;
|
|
565
|
+
WATCH_FOLDER: string;
|
|
566
|
+
REPO_ROOT: string;
|
|
567
|
+
FFMPEG_PATH: string;
|
|
568
|
+
FFPROBE_PATH: string;
|
|
569
|
+
EXA_API_KEY: string;
|
|
570
|
+
EXA_MCP_URL: string;
|
|
571
|
+
YOUTUBE_API_KEY: string;
|
|
572
|
+
PERPLEXITY_API_KEY: string;
|
|
573
|
+
LLM_PROVIDER: string;
|
|
574
|
+
LLM_MODEL: string;
|
|
575
|
+
ANTHROPIC_API_KEY: string;
|
|
576
|
+
OUTPUT_DIR: string;
|
|
577
|
+
BRAND_PATH: string;
|
|
578
|
+
VERBOSE: boolean;
|
|
579
|
+
SKIP_GIT: boolean;
|
|
580
|
+
SKIP_SILENCE_REMOVAL: boolean;
|
|
581
|
+
SKIP_SHORTS: boolean;
|
|
582
|
+
SKIP_MEDIUM_CLIPS: boolean;
|
|
583
|
+
SKIP_SOCIAL: boolean;
|
|
584
|
+
SKIP_CAPTIONS: boolean;
|
|
585
|
+
SKIP_VISUAL_ENHANCEMENT: boolean;
|
|
586
|
+
LATE_API_KEY: string;
|
|
587
|
+
LATE_PROFILE_ID: string;
|
|
588
|
+
SKIP_SOCIAL_PUBLISH: boolean;
|
|
589
|
+
GEMINI_API_KEY: string;
|
|
590
|
+
GEMINI_MODEL: string;
|
|
591
|
+
/** GitHub repository for idea tracking (format: owner/repo) */
|
|
592
|
+
IDEAS_REPO: string;
|
|
593
|
+
/** GitHub Personal Access Token with repo + project scopes */
|
|
594
|
+
GITHUB_TOKEN: string;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
interface GlobalCredentials {
|
|
598
|
+
openaiApiKey?: string;
|
|
599
|
+
anthropicApiKey?: string;
|
|
600
|
+
exaApiKey?: string;
|
|
601
|
+
youtubeApiKey?: string;
|
|
602
|
+
perplexityApiKey?: string;
|
|
603
|
+
lateApiKey?: string;
|
|
604
|
+
githubToken?: string;
|
|
605
|
+
geminiApiKey?: string;
|
|
606
|
+
}
|
|
607
|
+
interface GlobalDefaults {
|
|
608
|
+
llmProvider?: string;
|
|
609
|
+
llmModel?: string;
|
|
610
|
+
outputDir?: string;
|
|
611
|
+
watchFolder?: string;
|
|
612
|
+
brandPath?: string;
|
|
613
|
+
ideasRepo?: string;
|
|
614
|
+
lateProfileId?: string;
|
|
615
|
+
geminiModel?: string;
|
|
616
|
+
}
|
|
617
|
+
interface GlobalConfig {
|
|
618
|
+
credentials: GlobalCredentials;
|
|
619
|
+
defaults: GlobalDefaults;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
type DayOfWeek = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
623
|
+
interface TimeSlot {
|
|
624
|
+
days: DayOfWeek[];
|
|
625
|
+
time: string;
|
|
626
|
+
label: string;
|
|
627
|
+
}
|
|
628
|
+
interface ClipTypeSchedule {
|
|
629
|
+
slots: TimeSlot[];
|
|
630
|
+
avoidDays: DayOfWeek[];
|
|
631
|
+
}
|
|
632
|
+
interface PlatformSchedule {
|
|
633
|
+
slots: TimeSlot[];
|
|
634
|
+
avoidDays: DayOfWeek[];
|
|
635
|
+
byClipType?: Record<string, ClipTypeSchedule>;
|
|
636
|
+
}
|
|
637
|
+
interface IdeaSpacingConfig {
|
|
638
|
+
samePlatformHours: number;
|
|
639
|
+
crossPlatformHours: number;
|
|
640
|
+
}
|
|
641
|
+
interface DisplacementConfig {
|
|
642
|
+
enabled: boolean;
|
|
643
|
+
canDisplace: 'non-idea-only';
|
|
644
|
+
}
|
|
645
|
+
interface ScheduleConfig {
|
|
646
|
+
timezone: string;
|
|
647
|
+
platforms: Record<string, PlatformSchedule>;
|
|
648
|
+
ideaSpacing?: IdeaSpacingConfig;
|
|
649
|
+
displacement?: DisplacementConfig;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Configuration options for the VidPipe SDK factory.
|
|
654
|
+
*
|
|
655
|
+
* All properties are optional so the SDK can be created with zero configuration.
|
|
656
|
+
*/
|
|
657
|
+
interface VidPipeConfig {
|
|
658
|
+
openaiApiKey?: string;
|
|
659
|
+
anthropicApiKey?: string;
|
|
660
|
+
exaApiKey?: string;
|
|
661
|
+
youtubeApiKey?: string;
|
|
662
|
+
perplexityApiKey?: string;
|
|
663
|
+
lateApiKey?: string;
|
|
664
|
+
lateProfileId?: string;
|
|
665
|
+
githubToken?: string;
|
|
666
|
+
geminiApiKey?: string;
|
|
667
|
+
llmProvider?: 'copilot' | 'openai' | 'claude';
|
|
668
|
+
llmModel?: string;
|
|
669
|
+
outputDir?: string;
|
|
670
|
+
watchFolder?: string;
|
|
671
|
+
brandPath?: string;
|
|
672
|
+
repoRoot?: string;
|
|
673
|
+
verbose?: boolean;
|
|
674
|
+
ideasRepo?: string;
|
|
675
|
+
geminiModel?: string;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Options for processing a video through the VidPipe pipeline.
|
|
679
|
+
*/
|
|
680
|
+
interface ProcessOptions {
|
|
681
|
+
/** Comma-separated idea issue numbers to link to this video */
|
|
682
|
+
ideas?: number[];
|
|
683
|
+
/** Skip specific pipeline stages */
|
|
684
|
+
skipGit?: boolean;
|
|
685
|
+
/** Skip the silence removal stage */
|
|
686
|
+
skipSilenceRemoval?: boolean;
|
|
687
|
+
/** Skip short clip generation */
|
|
688
|
+
skipShorts?: boolean;
|
|
689
|
+
/** Skip medium clip generation */
|
|
690
|
+
skipMediumClips?: boolean;
|
|
691
|
+
/** Skip social content generation */
|
|
692
|
+
skipSocial?: boolean;
|
|
693
|
+
/** Skip caption generation and caption burn steps */
|
|
694
|
+
skipCaptions?: boolean;
|
|
695
|
+
/** Skip visual enhancement processing */
|
|
696
|
+
skipVisualEnhancement?: boolean;
|
|
697
|
+
/** Skip publishing generated social content */
|
|
698
|
+
skipSocialPublish?: boolean;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Options for AI-assisted idea generation.
|
|
702
|
+
*/
|
|
703
|
+
interface IdeateOptions {
|
|
704
|
+
/** Seed topics for idea generation */
|
|
705
|
+
topics?: string[];
|
|
706
|
+
/** Number of ideas to generate */
|
|
707
|
+
count?: number;
|
|
708
|
+
/** Path to brand.json config */
|
|
709
|
+
brandPath?: string;
|
|
710
|
+
/** When true, allows count=1 (bypasses minimum idea count). Used for single-topic idea creation. */
|
|
711
|
+
singleTopic?: boolean;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Options for finding the next scheduling slot.
|
|
715
|
+
*/
|
|
716
|
+
interface SlotOptions {
|
|
717
|
+
/** Idea issue numbers for spacing rules */
|
|
718
|
+
ideaIds?: number[];
|
|
719
|
+
/** Urgency deadline (ISO 8601 date) */
|
|
720
|
+
publishBy?: string;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Options for schedule realignment.
|
|
724
|
+
*/
|
|
725
|
+
interface RealignOptions {
|
|
726
|
+
/** Filter to specific platform */
|
|
727
|
+
platform?: string;
|
|
728
|
+
/** Preview only, don't execute */
|
|
729
|
+
dryRun?: boolean;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* A single diagnostic check returned by the SDK doctor command.
|
|
733
|
+
*/
|
|
734
|
+
interface DiagnosticCheck {
|
|
735
|
+
name: string;
|
|
736
|
+
status: 'pass' | 'fail' | 'warn';
|
|
737
|
+
message: string;
|
|
738
|
+
details?: string;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Aggregate result returned by the SDK doctor command.
|
|
742
|
+
*/
|
|
743
|
+
interface DiagnosticResult {
|
|
744
|
+
checks: DiagnosticCheck[];
|
|
745
|
+
allPassed: boolean;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Union of clip result types produced by VidPipe.
|
|
749
|
+
*/
|
|
750
|
+
type GeneratedClip = ShortClip | MediumClip;
|
|
751
|
+
/**
|
|
752
|
+
* Main VidPipe SDK interface.
|
|
753
|
+
*/
|
|
754
|
+
interface VidPipeSDK {
|
|
755
|
+
/** Run the full video processing pipeline */
|
|
756
|
+
processVideo(videoPath: string, options?: ProcessOptions): Promise<PipelineResult>;
|
|
757
|
+
/** Generate AI-powered content ideas */
|
|
758
|
+
ideate(options?: IdeateOptions): Promise<Idea[]>;
|
|
759
|
+
/** Idea management */
|
|
760
|
+
ideas: {
|
|
761
|
+
list(filters?: IdeaFilters): Promise<Idea[]>;
|
|
762
|
+
get(issueNumber: number): Promise<Idea | null>;
|
|
763
|
+
create(input: CreateIdeaInput): Promise<Idea>;
|
|
764
|
+
update(issueNumber: number, updates: Partial<CreateIdeaInput>): Promise<Idea>;
|
|
765
|
+
};
|
|
766
|
+
/** Schedule management */
|
|
767
|
+
schedule: {
|
|
768
|
+
findNextSlot(platform: string, clipType?: string, options?: SlotOptions): Promise<string | null>;
|
|
769
|
+
getCalendar(startDate?: Date, endDate?: Date): Promise<ScheduleSlot[]>;
|
|
770
|
+
realign(options?: RealignOptions): Promise<{
|
|
771
|
+
moved: number;
|
|
772
|
+
skipped: number;
|
|
773
|
+
}>;
|
|
774
|
+
loadConfig(): Promise<ScheduleConfig>;
|
|
775
|
+
};
|
|
776
|
+
/** Video operations */
|
|
777
|
+
video: {
|
|
778
|
+
extractClip(videoPath: string, start: number, end: number, output: string): Promise<string>;
|
|
779
|
+
burnCaptions(videoPath: string, captionsFile: string, output: string): Promise<string>;
|
|
780
|
+
detectSilence(videoPath: string, options?: {
|
|
781
|
+
threshold?: string;
|
|
782
|
+
minDuration?: number;
|
|
783
|
+
}): Promise<Array<{
|
|
784
|
+
start: number;
|
|
785
|
+
end: number;
|
|
786
|
+
}>>;
|
|
787
|
+
generateVariants(videoPath: string, platforms: Platform[], outputDir: string): Promise<Array<{
|
|
788
|
+
platform: Platform;
|
|
789
|
+
path: string;
|
|
790
|
+
}>>;
|
|
791
|
+
captureFrame(videoPath: string, timestamp: number, output: string): Promise<string>;
|
|
792
|
+
};
|
|
793
|
+
/** Social media operations */
|
|
794
|
+
social: {
|
|
795
|
+
generatePosts(context: {
|
|
796
|
+
title: string;
|
|
797
|
+
description: string;
|
|
798
|
+
tags: string[];
|
|
799
|
+
}, platforms: Platform[]): Promise<SocialPost[]>;
|
|
800
|
+
};
|
|
801
|
+
/** Run diagnostic checks */
|
|
802
|
+
doctor(): Promise<DiagnosticResult>;
|
|
803
|
+
/** Configuration access */
|
|
804
|
+
config: {
|
|
805
|
+
get(key: string): string | boolean | undefined;
|
|
806
|
+
getAll(): AppEnvironment;
|
|
807
|
+
getGlobal(): GlobalConfig;
|
|
808
|
+
set(key: string, value: string | boolean): void;
|
|
809
|
+
save(): Promise<void>;
|
|
810
|
+
path(): string;
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
declare function createVidPipe(sdkConfig?: VidPipeConfig): VidPipeSDK;
|
|
815
|
+
|
|
816
|
+
export { type AgentResult, type AspectRatio, type CaptionStyle, type Chapter, type CreateIdeaInput, type DiagnosticCheck, type DiagnosticResult, type EmotionalTrigger, type EnhancementOpportunity, type GeneratedClip, type GeneratedOverlay, type HookType, type Idea, type IdeaCommentData, type IdeaFilters, type IdeaPublishRecord, type IdeaStatus, type IdeateOptions, type MediumClip, type MediumClipType, type MediumNarrativeStructure, type MediumSegment, type OverlayPlacement, type OverlayRegion, PLATFORM_CHAR_LIMITS, type PipelineResult, PipelineStage, Platform, type ProcessOptions, type RealignOptions, SUPPORTED_VIDEO_EXTENSIONS, type ScheduleSlot, type ScreenRegion, type Segment, type ShortClip, type ShortClipVariant, type ShortNarrativeStructure, type ShortSegment, type SilenceRemovalResult, type SlotOptions, type SocialPost, type StageResult, type Transcript, type VidPipeConfig, type VidPipeSDK, type VideoFile, type VideoLayout, type VideoPlatform, type VideoSnapshot, type VideoSummary, type VisualEnhancementResult, type WebcamRegion, type Word, createVidPipe, fromLatePlatform, normalizePlatformString, toLatePlatform };
|