vidpipe 1.0.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.
- package/README.md +243 -0
- package/assets/fonts/Montserrat-Bold.ttf +0 -0
- package/assets/fonts/Montserrat-Regular.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/dist/__tests__/agents.test.d.ts +2 -0
- package/dist/__tests__/agents.test.d.ts.map +1 -0
- package/dist/__tests__/agents.test.js +434 -0
- package/dist/__tests__/agents.test.js.map +1 -0
- package/dist/__tests__/aspectRatio.test.d.ts +2 -0
- package/dist/__tests__/aspectRatio.test.d.ts.map +1 -0
- package/dist/__tests__/aspectRatio.test.js +406 -0
- package/dist/__tests__/aspectRatio.test.js.map +1 -0
- package/dist/__tests__/captionGenerator.test.d.ts +2 -0
- package/dist/__tests__/captionGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/captionGenerator.test.js +435 -0
- package/dist/__tests__/captionGenerator.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +81 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/faceDetection.test.d.ts +2 -0
- package/dist/__tests__/faceDetection.test.d.ts.map +1 -0
- package/dist/__tests__/faceDetection.test.js +372 -0
- package/dist/__tests__/faceDetection.test.js.map +1 -0
- package/dist/__tests__/ffmpegTools.test.d.ts +2 -0
- package/dist/__tests__/ffmpegTools.test.d.ts.map +1 -0
- package/dist/__tests__/ffmpegTools.test.js +464 -0
- package/dist/__tests__/ffmpegTools.test.js.map +1 -0
- package/dist/__tests__/integration/captionBurn.test.d.ts +2 -0
- package/dist/__tests__/integration/captionBurn.test.d.ts.map +1 -0
- package/dist/__tests__/integration/captionBurn.test.js +103 -0
- package/dist/__tests__/integration/captionBurn.test.js.map +1 -0
- package/dist/__tests__/integration/clipComposite.test.d.ts +2 -0
- package/dist/__tests__/integration/clipComposite.test.d.ts.map +1 -0
- package/dist/__tests__/integration/clipComposite.test.js +56 -0
- package/dist/__tests__/integration/clipComposite.test.js.map +1 -0
- package/dist/__tests__/integration/faceDetection.test.d.ts +2 -0
- package/dist/__tests__/integration/faceDetection.test.d.ts.map +1 -0
- package/dist/__tests__/integration/faceDetection.test.js +85 -0
- package/dist/__tests__/integration/faceDetection.test.js.map +1 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.d.ts +2 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.d.ts.map +1 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.js +88 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.js.map +1 -0
- package/dist/__tests__/integration/fixture.d.ts +19 -0
- package/dist/__tests__/integration/fixture.d.ts.map +1 -0
- package/dist/__tests__/integration/fixture.js +112 -0
- package/dist/__tests__/integration/fixture.js.map +1 -0
- package/dist/__tests__/integration/fixture.test.d.ts +2 -0
- package/dist/__tests__/integration/fixture.test.d.ts.map +1 -0
- package/dist/__tests__/integration/fixture.test.js +27 -0
- package/dist/__tests__/integration/fixture.test.js.map +1 -0
- package/dist/__tests__/integration/realCaptions.test.d.ts +2 -0
- package/dist/__tests__/integration/realCaptions.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realCaptions.test.js +226 -0
- package/dist/__tests__/integration/realCaptions.test.js.map +1 -0
- package/dist/__tests__/integration/realPipeline.test.d.ts +2 -0
- package/dist/__tests__/integration/realPipeline.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realPipeline.test.js +210 -0
- package/dist/__tests__/integration/realPipeline.test.js.map +1 -0
- package/dist/__tests__/integration/silenceRemoval.test.d.ts +2 -0
- package/dist/__tests__/integration/silenceRemoval.test.d.ts.map +1 -0
- package/dist/__tests__/integration/silenceRemoval.test.js +93 -0
- package/dist/__tests__/integration/silenceRemoval.test.js.map +1 -0
- package/dist/__tests__/pipeline.test.d.ts +2 -0
- package/dist/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.test.js +434 -0
- package/dist/__tests__/pipeline.test.js.map +1 -0
- package/dist/__tests__/services.test.d.ts +2 -0
- package/dist/__tests__/services.test.d.ts.map +1 -0
- package/dist/__tests__/services.test.js +655 -0
- package/dist/__tests__/services.test.js.map +1 -0
- package/dist/__tests__/silenceRemoval.test.d.ts +2 -0
- package/dist/__tests__/silenceRemoval.test.d.ts.map +1 -0
- package/dist/__tests__/silenceRemoval.test.js +266 -0
- package/dist/__tests__/silenceRemoval.test.js.map +1 -0
- package/dist/__tests__/singlePassEdit.test.d.ts +2 -0
- package/dist/__tests__/singlePassEdit.test.d.ts.map +1 -0
- package/dist/__tests__/singlePassEdit.test.js +321 -0
- package/dist/__tests__/singlePassEdit.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +8 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/__tests__/utilities.test.d.ts +2 -0
- package/dist/__tests__/utilities.test.d.ts.map +1 -0
- package/dist/__tests__/utilities.test.js +268 -0
- package/dist/__tests__/utilities.test.js.map +1 -0
- package/dist/agents/BaseAgent.d.ts +52 -0
- package/dist/agents/BaseAgent.d.ts.map +1 -0
- package/dist/agents/BaseAgent.js +108 -0
- package/dist/agents/BaseAgent.js.map +1 -0
- package/dist/agents/BlogAgent.d.ts +3 -0
- package/dist/agents/BlogAgent.d.ts.map +1 -0
- package/dist/agents/BlogAgent.js +163 -0
- package/dist/agents/BlogAgent.js.map +1 -0
- package/dist/agents/ChapterAgent.d.ts +11 -0
- package/dist/agents/ChapterAgent.d.ts.map +1 -0
- package/dist/agents/ChapterAgent.js +191 -0
- package/dist/agents/ChapterAgent.js.map +1 -0
- package/dist/agents/MediumVideoAgent.d.ts +3 -0
- package/dist/agents/MediumVideoAgent.d.ts.map +1 -0
- package/dist/agents/MediumVideoAgent.js +219 -0
- package/dist/agents/MediumVideoAgent.js.map +1 -0
- package/dist/agents/ShortsAgent.d.ts +3 -0
- package/dist/agents/ShortsAgent.d.ts.map +1 -0
- package/dist/agents/ShortsAgent.js +243 -0
- package/dist/agents/ShortsAgent.js.map +1 -0
- package/dist/agents/SilenceRemovalAgent.d.ts +9 -0
- package/dist/agents/SilenceRemovalAgent.d.ts.map +1 -0
- package/dist/agents/SilenceRemovalAgent.js +208 -0
- package/dist/agents/SilenceRemovalAgent.js.map +1 -0
- package/dist/agents/SocialMediaAgent.d.ts +4 -0
- package/dist/agents/SocialMediaAgent.d.ts.map +1 -0
- package/dist/agents/SocialMediaAgent.js +248 -0
- package/dist/agents/SocialMediaAgent.js.map +1 -0
- package/dist/agents/SummaryAgent.d.ts +11 -0
- package/dist/agents/SummaryAgent.d.ts.map +1 -0
- package/dist/agents/SummaryAgent.js +333 -0
- package/dist/agents/SummaryAgent.js.map +1 -0
- package/dist/config/brand.d.ts +29 -0
- package/dist/config/brand.d.ts.map +1 -0
- package/dist/config/brand.js +83 -0
- package/dist/config/brand.js.map +1 -0
- package/dist/config/environment.d.ts +36 -0
- package/dist/config/environment.d.ts.map +1 -0
- package/dist/config/environment.js +44 -0
- package/dist/config/environment.js.map +1 -0
- package/dist/config/logger.d.ts +5 -0
- package/dist/config/logger.d.ts.map +1 -0
- package/dist/config/logger.js +13 -0
- package/dist/config/logger.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline.d.ts +57 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +287 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/services/captionGeneration.d.ts +7 -0
- package/dist/services/captionGeneration.d.ts.map +1 -0
- package/dist/services/captionGeneration.js +29 -0
- package/dist/services/captionGeneration.js.map +1 -0
- package/dist/services/fileWatcher.d.ts +19 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +120 -0
- package/dist/services/fileWatcher.js.map +1 -0
- package/dist/services/gitOperations.d.ts +3 -0
- package/dist/services/gitOperations.d.ts.map +1 -0
- package/dist/services/gitOperations.js +43 -0
- package/dist/services/gitOperations.js.map +1 -0
- package/dist/services/socialPosting.d.ts +38 -0
- package/dist/services/socialPosting.d.ts.map +1 -0
- package/dist/services/socialPosting.js +102 -0
- package/dist/services/socialPosting.js.map +1 -0
- package/dist/services/transcription.d.ts +3 -0
- package/dist/services/transcription.d.ts.map +1 -0
- package/dist/services/transcription.js +100 -0
- package/dist/services/transcription.js.map +1 -0
- package/dist/services/videoIngestion.d.ts +3 -0
- package/dist/services/videoIngestion.d.ts.map +1 -0
- package/dist/services/videoIngestion.js +103 -0
- package/dist/services/videoIngestion.js.map +1 -0
- package/dist/tools/captions/captionGenerator.d.ts +84 -0
- package/dist/tools/captions/captionGenerator.d.ts.map +1 -0
- package/dist/tools/captions/captionGenerator.js +390 -0
- package/dist/tools/captions/captionGenerator.js.map +1 -0
- package/dist/tools/ffmpeg/aspectRatio.d.ts +101 -0
- package/dist/tools/ffmpeg/aspectRatio.d.ts.map +1 -0
- package/dist/tools/ffmpeg/aspectRatio.js +338 -0
- package/dist/tools/ffmpeg/aspectRatio.js.map +1 -0
- package/dist/tools/ffmpeg/audioExtraction.d.ts +16 -0
- package/dist/tools/ffmpeg/audioExtraction.d.ts.map +1 -0
- package/dist/tools/ffmpeg/audioExtraction.js +86 -0
- package/dist/tools/ffmpeg/audioExtraction.js.map +1 -0
- package/dist/tools/ffmpeg/captionBurning.d.ts +8 -0
- package/dist/tools/ffmpeg/captionBurning.d.ts.map +1 -0
- package/dist/tools/ffmpeg/captionBurning.js +71 -0
- package/dist/tools/ffmpeg/captionBurning.js.map +1 -0
- package/dist/tools/ffmpeg/clipExtraction.d.ts +23 -0
- package/dist/tools/ffmpeg/clipExtraction.d.ts.map +1 -0
- package/dist/tools/ffmpeg/clipExtraction.js +178 -0
- package/dist/tools/ffmpeg/clipExtraction.js.map +1 -0
- package/dist/tools/ffmpeg/faceDetection.d.ts +127 -0
- package/dist/tools/ffmpeg/faceDetection.d.ts.map +1 -0
- package/dist/tools/ffmpeg/faceDetection.js +500 -0
- package/dist/tools/ffmpeg/faceDetection.js.map +1 -0
- package/dist/tools/ffmpeg/frameCapture.d.ts +10 -0
- package/dist/tools/ffmpeg/frameCapture.d.ts.map +1 -0
- package/dist/tools/ffmpeg/frameCapture.js +48 -0
- package/dist/tools/ffmpeg/frameCapture.js.map +1 -0
- package/dist/tools/ffmpeg/silenceDetection.d.ts +10 -0
- package/dist/tools/ffmpeg/silenceDetection.d.ts.map +1 -0
- package/dist/tools/ffmpeg/silenceDetection.js +55 -0
- package/dist/tools/ffmpeg/silenceDetection.js.map +1 -0
- package/dist/tools/ffmpeg/singlePassEdit.d.ts +25 -0
- package/dist/tools/ffmpeg/singlePassEdit.d.ts.map +1 -0
- package/dist/tools/ffmpeg/singlePassEdit.js +123 -0
- package/dist/tools/ffmpeg/singlePassEdit.js.map +1 -0
- package/dist/tools/search/exaClient.d.ts +8 -0
- package/dist/tools/search/exaClient.d.ts.map +1 -0
- package/dist/tools/search/exaClient.js +38 -0
- package/dist/tools/search/exaClient.js.map +1 -0
- package/dist/tools/whisper/whisperClient.d.ts +3 -0
- package/dist/tools/whisper/whisperClient.d.ts.map +1 -0
- package/dist/tools/whisper/whisperClient.js +77 -0
- package/dist/tools/whisper/whisperClient.js.map +1 -0
- package/dist/types/index.d.ts +305 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +44 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { initConfig, validateRequiredKeys, getConfig } from './config/environment';
|
|
4
|
+
import { FileWatcher } from './services/fileWatcher';
|
|
5
|
+
import { processVideoSafe } from './pipeline';
|
|
6
|
+
import logger, { setVerbose } from './config/logger';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
12
|
+
const BANNER = `
|
|
13
|
+
╔══════════════════════════════════════╗
|
|
14
|
+
║ Video Auto Note Taker v${pkg.version.padEnd(10)}║
|
|
15
|
+
╚══════════════════════════════════════╝
|
|
16
|
+
`;
|
|
17
|
+
const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name('video-auto-note-taker')
|
|
20
|
+
.description('Automatically process videos: transcribe, summarize, generate shorts, captions, and social posts')
|
|
21
|
+
.version(pkg.version, '-V, --version')
|
|
22
|
+
.argument('[video-path]', 'Path to a video file to process (implies --once)')
|
|
23
|
+
.option('--watch-dir <path>', 'Folder to watch for new recordings (default: env WATCH_FOLDER)')
|
|
24
|
+
.option('--output-dir <path>', 'Output directory for processed videos (default: ./recordings)')
|
|
25
|
+
.option('--openai-key <key>', 'OpenAI API key (default: env OPENAI_API_KEY)')
|
|
26
|
+
.option('--exa-key <key>', 'Exa AI API key for web search (default: env EXA_API_KEY)')
|
|
27
|
+
.option('--once', 'Process a single video and exit (no watching)')
|
|
28
|
+
.option('--brand <path>', 'Path to brand.json config (default: ./brand.json)')
|
|
29
|
+
.option('--no-git', 'Skip git commit/push stage')
|
|
30
|
+
.option('--no-silence-removal', 'Skip silence removal stage')
|
|
31
|
+
.option('--no-shorts', 'Skip shorts generation')
|
|
32
|
+
.option('--no-medium-clips', 'Skip medium clip generation')
|
|
33
|
+
.option('--no-social', 'Skip social media post generation')
|
|
34
|
+
.option('--no-captions', 'Skip caption generation/burning')
|
|
35
|
+
.option('-v, --verbose', 'Verbose logging');
|
|
36
|
+
program.parse();
|
|
37
|
+
const opts = program.opts();
|
|
38
|
+
const videoArg = program.args[0];
|
|
39
|
+
const onceMode = opts.once || !!videoArg;
|
|
40
|
+
const cliOptions = {
|
|
41
|
+
watchDir: opts.watchDir,
|
|
42
|
+
outputDir: opts.outputDir,
|
|
43
|
+
openaiKey: opts.openaiKey,
|
|
44
|
+
exaKey: opts.exaKey,
|
|
45
|
+
brand: opts.brand,
|
|
46
|
+
verbose: opts.verbose,
|
|
47
|
+
git: opts.git,
|
|
48
|
+
silenceRemoval: opts.silenceRemoval,
|
|
49
|
+
shorts: opts.shorts,
|
|
50
|
+
mediumClips: opts.mediumClips,
|
|
51
|
+
social: opts.social,
|
|
52
|
+
captions: opts.captions,
|
|
53
|
+
};
|
|
54
|
+
const queue = [];
|
|
55
|
+
let processing = false;
|
|
56
|
+
let shutdownRequested = false;
|
|
57
|
+
let watcher = null;
|
|
58
|
+
async function processQueue() {
|
|
59
|
+
if (processing || queue.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
processing = true;
|
|
62
|
+
try {
|
|
63
|
+
while (queue.length > 0) {
|
|
64
|
+
const videoPath = queue.shift();
|
|
65
|
+
logger.info(`Processing video: ${videoPath}`);
|
|
66
|
+
await processVideoSafe(videoPath);
|
|
67
|
+
if (onceMode) {
|
|
68
|
+
logger.info('--once flag set, exiting after first video.');
|
|
69
|
+
await shutdown();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (shutdownRequested)
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
processing = false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function enqueue(videoPath) {
|
|
81
|
+
queue.push(videoPath);
|
|
82
|
+
logger.info(`Queued video: ${videoPath} (queue length: ${queue.length})`);
|
|
83
|
+
processQueue().catch(err => logger.error('Queue processing error:', err));
|
|
84
|
+
}
|
|
85
|
+
async function shutdown() {
|
|
86
|
+
if (shutdownRequested)
|
|
87
|
+
return;
|
|
88
|
+
shutdownRequested = true;
|
|
89
|
+
logger.info('Shutting down...');
|
|
90
|
+
if (watcher) {
|
|
91
|
+
watcher.stop();
|
|
92
|
+
}
|
|
93
|
+
while (processing) {
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
95
|
+
}
|
|
96
|
+
logger.info('Goodbye.');
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
async function main() {
|
|
100
|
+
logger.info(BANNER);
|
|
101
|
+
initConfig(cliOptions);
|
|
102
|
+
if (opts.verbose)
|
|
103
|
+
setVerbose();
|
|
104
|
+
validateRequiredKeys();
|
|
105
|
+
const config = getConfig();
|
|
106
|
+
logger.info(`Watch folder: ${config.WATCH_FOLDER}`);
|
|
107
|
+
logger.info(`Output dir: ${config.OUTPUT_DIR}`);
|
|
108
|
+
// Direct file mode: process a specific video and exit
|
|
109
|
+
if (videoArg) {
|
|
110
|
+
const resolvedPath = path.resolve(videoArg);
|
|
111
|
+
logger.info(`Processing single video: ${resolvedPath}`);
|
|
112
|
+
await processVideoSafe(resolvedPath);
|
|
113
|
+
logger.info('Done.');
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
// Watch mode
|
|
117
|
+
watcher = new FileWatcher();
|
|
118
|
+
watcher.on('new-video', (filePath) => {
|
|
119
|
+
enqueue(filePath);
|
|
120
|
+
});
|
|
121
|
+
watcher.start();
|
|
122
|
+
if (onceMode) {
|
|
123
|
+
logger.info('Running in --once mode. Will exit after processing the next video.');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
logger.info('Watching for new videos. Press Ctrl+C to stop.');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
process.on('SIGINT', () => shutdown());
|
|
130
|
+
process.on('SIGTERM', () => shutdown());
|
|
131
|
+
main().catch((err) => {
|
|
132
|
+
logger.error(`Fatal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Transcript, StageResult, PipelineResult, PipelineStage } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a single pipeline stage with error isolation and timing.
|
|
4
|
+
*
|
|
5
|
+
* ### Stage contract
|
|
6
|
+
* - Each stage is wrapped in a try/catch so a failure **does not abort** the
|
|
7
|
+
* pipeline. Subsequent stages proceed with whatever data is available.
|
|
8
|
+
* - Returns `undefined` on failure (callers must null-check before using the result).
|
|
9
|
+
* - Records success/failure, error message, and wall-clock duration in `stageResults`
|
|
10
|
+
* for the pipeline summary.
|
|
11
|
+
*
|
|
12
|
+
* This design lets the pipeline produce partial results — e.g. if shorts
|
|
13
|
+
* generation fails, the summary and social posts can still be generated
|
|
14
|
+
* from the transcript.
|
|
15
|
+
*
|
|
16
|
+
* @param stageName - Enum value identifying the stage (used in logs and results)
|
|
17
|
+
* @param fn - Async function that performs the stage's work
|
|
18
|
+
* @param stageResults - Mutable array that accumulates per-stage outcome records
|
|
19
|
+
* @returns The stage result on success, or `undefined` on failure
|
|
20
|
+
*/
|
|
21
|
+
export declare function runStage<T>(stageName: PipelineStage, fn: () => Promise<T>, stageResults: StageResult[]): Promise<T | undefined>;
|
|
22
|
+
/**
|
|
23
|
+
* Adjust transcript timestamps to account for removed silence segments.
|
|
24
|
+
* Shifts all timestamps by subtracting the cumulative removed duration before each point.
|
|
25
|
+
*/
|
|
26
|
+
export declare function adjustTranscript(transcript: Transcript, removals: {
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
}[]): Transcript;
|
|
30
|
+
/**
|
|
31
|
+
* Run the full video processing pipeline.
|
|
32
|
+
*
|
|
33
|
+
* ### Stage ordering and data flow
|
|
34
|
+
* 1. **Ingest** — extracts metadata (slug, duration, paths). Required; aborts if failed.
|
|
35
|
+
* 2. **Transcribe** — Whisper transcription with word-level timestamps.
|
|
36
|
+
* 3. **Silence removal** — trims dead air from the video and adjusts the transcript
|
|
37
|
+
* timestamps accordingly. Produces an `adjustedTranscript` for captions.
|
|
38
|
+
* 4. **Captions** — generates SRT/VTT/ASS files from the (adjusted) transcript.
|
|
39
|
+
* 5. **Caption burn** — renders captions into the video using FFmpeg. Prefers a
|
|
40
|
+
* single-pass approach (silence removal + captions in one encode) when possible.
|
|
41
|
+
* 6. **Shorts** — AI-selected short clips. Uses the **original** transcript because
|
|
42
|
+
* clips are cut from the original (unedited) video.
|
|
43
|
+
* 7. **Medium clips** — longer AI-selected clips (same original-transcript reasoning).
|
|
44
|
+
* 8. **Chapters** — topic-boundary detection for YouTube chapters.
|
|
45
|
+
* 9. **Summary** — README generation (runs after shorts/chapters so it can reference them).
|
|
46
|
+
* 10–12. **Social posts** — platform-specific posts for the full video and each clip.
|
|
47
|
+
* 13. **Blog** — long-form blog post from transcript + summary.
|
|
48
|
+
* 14. **Git push** — commits all generated assets and pushes.
|
|
49
|
+
*
|
|
50
|
+
* ### Why failures don't abort
|
|
51
|
+
* Each stage runs through {@link runStage} which catches errors. This means a
|
|
52
|
+
* transcription failure still lets git-push run (committing whatever was produced),
|
|
53
|
+
* and a shorts failure doesn't block summary generation.
|
|
54
|
+
*/
|
|
55
|
+
export declare function processVideo(videoPath: string): Promise<PipelineResult>;
|
|
56
|
+
export declare function processVideoSafe(videoPath: string): Promise<PipelineResult | null>;
|
|
57
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,UAAU,EAKV,WAAW,EACX,cAAc,EACd,aAAa,EAGd,MAAM,SAAS,CAAA;AAGhB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,SAAS,EAAE,aAAa,EACxB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,YAAY,EAAE,WAAW,EAAE,GAC1B,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAexB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GACzC,UAAU,CAmCZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAwM7E;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAQxF"}
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import logger from './config/logger';
|
|
4
|
+
import { getConfig } from './config/environment';
|
|
5
|
+
import { ingestVideo } from './services/videoIngestion';
|
|
6
|
+
import { transcribeVideo } from './services/transcription';
|
|
7
|
+
import { generateCaptions } from './services/captionGeneration';
|
|
8
|
+
import { generateSummary } from './agents/SummaryAgent';
|
|
9
|
+
import { generateShorts } from './agents/ShortsAgent';
|
|
10
|
+
import { generateMediumClips } from './agents/MediumVideoAgent';
|
|
11
|
+
import { generateSocialPosts, generateShortPosts } from './agents/SocialMediaAgent';
|
|
12
|
+
import { generateBlogPost } from './agents/BlogAgent';
|
|
13
|
+
import { generateChapters } from './agents/ChapterAgent';
|
|
14
|
+
import { commitAndPush } from './services/gitOperations';
|
|
15
|
+
import { removeDeadSilence } from './agents/SilenceRemovalAgent';
|
|
16
|
+
import { burnCaptions } from './tools/ffmpeg/captionBurning';
|
|
17
|
+
import { singlePassEditAndCaption } from './tools/ffmpeg/singlePassEdit';
|
|
18
|
+
import { PipelineStage as Stage } from './types';
|
|
19
|
+
/**
|
|
20
|
+
* Execute a single pipeline stage with error isolation and timing.
|
|
21
|
+
*
|
|
22
|
+
* ### Stage contract
|
|
23
|
+
* - Each stage is wrapped in a try/catch so a failure **does not abort** the
|
|
24
|
+
* pipeline. Subsequent stages proceed with whatever data is available.
|
|
25
|
+
* - Returns `undefined` on failure (callers must null-check before using the result).
|
|
26
|
+
* - Records success/failure, error message, and wall-clock duration in `stageResults`
|
|
27
|
+
* for the pipeline summary.
|
|
28
|
+
*
|
|
29
|
+
* This design lets the pipeline produce partial results — e.g. if shorts
|
|
30
|
+
* generation fails, the summary and social posts can still be generated
|
|
31
|
+
* from the transcript.
|
|
32
|
+
*
|
|
33
|
+
* @param stageName - Enum value identifying the stage (used in logs and results)
|
|
34
|
+
* @param fn - Async function that performs the stage's work
|
|
35
|
+
* @param stageResults - Mutable array that accumulates per-stage outcome records
|
|
36
|
+
* @returns The stage result on success, or `undefined` on failure
|
|
37
|
+
*/
|
|
38
|
+
export async function runStage(stageName, fn, stageResults) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
try {
|
|
41
|
+
const result = await fn();
|
|
42
|
+
const duration = Date.now() - start;
|
|
43
|
+
stageResults.push({ stage: stageName, success: true, duration });
|
|
44
|
+
logger.info(`Stage ${stageName} completed in ${duration}ms`);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const duration = Date.now() - start;
|
|
49
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50
|
+
stageResults.push({ stage: stageName, success: false, error: message, duration });
|
|
51
|
+
logger.error(`Stage ${stageName} failed after ${duration}ms: ${message}`);
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Adjust transcript timestamps to account for removed silence segments.
|
|
57
|
+
* Shifts all timestamps by subtracting the cumulative removed duration before each point.
|
|
58
|
+
*/
|
|
59
|
+
export function adjustTranscript(transcript, removals) {
|
|
60
|
+
const sorted = [...removals].sort((a, b) => a.start - b.start);
|
|
61
|
+
function adjustTime(t) {
|
|
62
|
+
let offset = 0;
|
|
63
|
+
for (const r of sorted) {
|
|
64
|
+
if (t <= r.start)
|
|
65
|
+
break;
|
|
66
|
+
if (t >= r.end) {
|
|
67
|
+
offset += r.end - r.start;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// timestamp is inside a removed region — snap to removal start
|
|
71
|
+
offset += t - r.start;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return t - offset;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
...transcript,
|
|
78
|
+
duration: adjustTime(transcript.duration),
|
|
79
|
+
segments: transcript.segments
|
|
80
|
+
.filter(seg => !sorted.some(r => seg.start >= r.start && seg.end <= r.end))
|
|
81
|
+
.map(seg => ({
|
|
82
|
+
...seg,
|
|
83
|
+
start: adjustTime(seg.start),
|
|
84
|
+
end: adjustTime(seg.end),
|
|
85
|
+
})),
|
|
86
|
+
words: transcript.words
|
|
87
|
+
.filter(w => !sorted.some(r => w.start >= r.start && w.end <= r.end))
|
|
88
|
+
.map(w => ({
|
|
89
|
+
...w,
|
|
90
|
+
start: adjustTime(w.start),
|
|
91
|
+
end: adjustTime(w.end),
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run the full video processing pipeline.
|
|
97
|
+
*
|
|
98
|
+
* ### Stage ordering and data flow
|
|
99
|
+
* 1. **Ingest** — extracts metadata (slug, duration, paths). Required; aborts if failed.
|
|
100
|
+
* 2. **Transcribe** — Whisper transcription with word-level timestamps.
|
|
101
|
+
* 3. **Silence removal** — trims dead air from the video and adjusts the transcript
|
|
102
|
+
* timestamps accordingly. Produces an `adjustedTranscript` for captions.
|
|
103
|
+
* 4. **Captions** — generates SRT/VTT/ASS files from the (adjusted) transcript.
|
|
104
|
+
* 5. **Caption burn** — renders captions into the video using FFmpeg. Prefers a
|
|
105
|
+
* single-pass approach (silence removal + captions in one encode) when possible.
|
|
106
|
+
* 6. **Shorts** — AI-selected short clips. Uses the **original** transcript because
|
|
107
|
+
* clips are cut from the original (unedited) video.
|
|
108
|
+
* 7. **Medium clips** — longer AI-selected clips (same original-transcript reasoning).
|
|
109
|
+
* 8. **Chapters** — topic-boundary detection for YouTube chapters.
|
|
110
|
+
* 9. **Summary** — README generation (runs after shorts/chapters so it can reference them).
|
|
111
|
+
* 10–12. **Social posts** — platform-specific posts for the full video and each clip.
|
|
112
|
+
* 13. **Blog** — long-form blog post from transcript + summary.
|
|
113
|
+
* 14. **Git push** — commits all generated assets and pushes.
|
|
114
|
+
*
|
|
115
|
+
* ### Why failures don't abort
|
|
116
|
+
* Each stage runs through {@link runStage} which catches errors. This means a
|
|
117
|
+
* transcription failure still lets git-push run (committing whatever was produced),
|
|
118
|
+
* and a shorts failure doesn't block summary generation.
|
|
119
|
+
*/
|
|
120
|
+
export async function processVideo(videoPath) {
|
|
121
|
+
const pipelineStart = Date.now();
|
|
122
|
+
const stageResults = [];
|
|
123
|
+
const cfg = getConfig();
|
|
124
|
+
logger.info(`Pipeline starting for: ${videoPath}`);
|
|
125
|
+
// 1. Ingestion — required for all subsequent stages
|
|
126
|
+
const video = await runStage(Stage.Ingestion, () => ingestVideo(videoPath), stageResults);
|
|
127
|
+
if (!video) {
|
|
128
|
+
const totalDuration = Date.now() - pipelineStart;
|
|
129
|
+
logger.error('Ingestion failed — cannot proceed without video metadata');
|
|
130
|
+
return { video: { originalPath: videoPath, repoPath: '', videoDir: '', slug: '', filename: '', duration: 0, size: 0, createdAt: new Date() }, transcript: undefined, editedVideoPath: undefined, captions: undefined, captionedVideoPath: undefined, summary: undefined, shorts: [], mediumClips: [], socialPosts: [], blogPost: undefined, stageResults, totalDuration };
|
|
131
|
+
}
|
|
132
|
+
// 2. Transcription
|
|
133
|
+
let transcript;
|
|
134
|
+
transcript = await runStage(Stage.Transcription, () => transcribeVideo(video), stageResults);
|
|
135
|
+
// 3. Silence Removal (context-aware)
|
|
136
|
+
let editedVideoPath;
|
|
137
|
+
let adjustedTranscript;
|
|
138
|
+
let silenceRemovals = [];
|
|
139
|
+
let silenceKeepSegments;
|
|
140
|
+
if (transcript && !cfg.SKIP_SILENCE_REMOVAL) {
|
|
141
|
+
const result = await runStage(Stage.SilenceRemoval, () => removeDeadSilence(video, transcript), stageResults);
|
|
142
|
+
if (result && result.wasEdited) {
|
|
143
|
+
editedVideoPath = result.editedPath;
|
|
144
|
+
silenceRemovals = result.removals;
|
|
145
|
+
silenceKeepSegments = result.keepSegments;
|
|
146
|
+
adjustedTranscript = adjustTranscript(transcript, silenceRemovals);
|
|
147
|
+
// Validate: check that adjusted transcript duration is close to edited video duration
|
|
148
|
+
const totalRemoved = silenceRemovals.reduce((sum, r) => sum + (r.end - r.start), 0);
|
|
149
|
+
const expectedDuration = transcript.duration - totalRemoved;
|
|
150
|
+
const adjustedDuration = adjustedTranscript.duration;
|
|
151
|
+
const drift = Math.abs(expectedDuration - adjustedDuration);
|
|
152
|
+
logger.info(`[Pipeline] Silence removal: original=${transcript.duration.toFixed(1)}s, removed=${totalRemoved.toFixed(1)}s, expected=${expectedDuration.toFixed(1)}s, adjusted=${adjustedDuration.toFixed(1)}s, drift=${drift.toFixed(1)}s`);
|
|
153
|
+
await fs.writeFile(path.join(video.videoDir, 'transcript-edited.json'), JSON.stringify(adjustedTranscript, null, 2));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Use adjusted transcript for captions (if silence was removed), original otherwise
|
|
157
|
+
const captionTranscript = adjustedTranscript ?? transcript;
|
|
158
|
+
// 4. Captions (fast, no AI needed) — generate from the right transcript
|
|
159
|
+
let captions;
|
|
160
|
+
if (captionTranscript && !cfg.SKIP_CAPTIONS) {
|
|
161
|
+
captions = await runStage(Stage.Captions, () => generateCaptions(video, captionTranscript), stageResults);
|
|
162
|
+
}
|
|
163
|
+
// 5. Caption Burn — use single-pass (silence removal + captions) when possible
|
|
164
|
+
let captionedVideoPath;
|
|
165
|
+
if (captions && !cfg.SKIP_CAPTIONS) {
|
|
166
|
+
const assFile = captions.find((p) => p.endsWith('.ass'));
|
|
167
|
+
if (assFile && silenceKeepSegments) {
|
|
168
|
+
// Single-pass: re-do silence removal + burn captions from ORIGINAL video in one encode
|
|
169
|
+
// This guarantees frame-accurate cuts with perfectly aligned captions
|
|
170
|
+
const captionedOutput = path.join(video.videoDir, `${video.slug}-captioned.mp4`);
|
|
171
|
+
captionedVideoPath = await runStage(Stage.CaptionBurn, () => singlePassEditAndCaption(video.repoPath, silenceKeepSegments, assFile, captionedOutput), stageResults);
|
|
172
|
+
}
|
|
173
|
+
else if (assFile) {
|
|
174
|
+
// No silence removal — just burn captions into original video
|
|
175
|
+
const videoToBurn = editedVideoPath ?? video.repoPath;
|
|
176
|
+
const captionedOutput = path.join(video.videoDir, `${video.slug}-captioned.mp4`);
|
|
177
|
+
captionedVideoPath = await runStage(Stage.CaptionBurn, () => burnCaptions(videoToBurn, assFile, captionedOutput), stageResults);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// 6. Shorts — use ORIGINAL transcript (shorts reference original video timestamps)
|
|
181
|
+
let shorts = [];
|
|
182
|
+
if (transcript && !cfg.SKIP_SHORTS) {
|
|
183
|
+
const result = await runStage(Stage.Shorts, () => generateShorts(video, transcript), stageResults);
|
|
184
|
+
if (result)
|
|
185
|
+
shorts = result;
|
|
186
|
+
}
|
|
187
|
+
// 7. Medium Clips — use ORIGINAL transcript (medium clips reference original video timestamps)
|
|
188
|
+
let mediumClips = [];
|
|
189
|
+
if (transcript && !cfg.SKIP_MEDIUM_CLIPS) {
|
|
190
|
+
const result = await runStage(Stage.MediumClips, () => generateMediumClips(video, transcript), stageResults);
|
|
191
|
+
if (result)
|
|
192
|
+
mediumClips = result;
|
|
193
|
+
}
|
|
194
|
+
// 8. Chapters — analyse transcript for topic boundaries
|
|
195
|
+
let chapters;
|
|
196
|
+
if (transcript) {
|
|
197
|
+
chapters = await runStage(Stage.Chapters, () => generateChapters(video, transcript), stageResults);
|
|
198
|
+
}
|
|
199
|
+
// 9. Summary (after shorts, medium clips, and chapters so the README can reference them)
|
|
200
|
+
let summary;
|
|
201
|
+
if (transcript) {
|
|
202
|
+
summary = await runStage(Stage.Summary, () => generateSummary(video, transcript, shorts, chapters), stageResults);
|
|
203
|
+
}
|
|
204
|
+
// 10. Social Media
|
|
205
|
+
let socialPosts = [];
|
|
206
|
+
if (transcript && summary && !cfg.SKIP_SOCIAL) {
|
|
207
|
+
const result = await runStage(Stage.SocialMedia, () => generateSocialPosts(video, transcript, summary, path.join(video.videoDir, 'social-posts')), stageResults);
|
|
208
|
+
if (result)
|
|
209
|
+
socialPosts = result;
|
|
210
|
+
}
|
|
211
|
+
// 11. Short Posts — generate social posts per short clip
|
|
212
|
+
if (transcript && shorts.length > 0 && !cfg.SKIP_SOCIAL) {
|
|
213
|
+
await runStage(Stage.ShortPosts, async () => {
|
|
214
|
+
for (const short of shorts) {
|
|
215
|
+
const posts = await generateShortPosts(video, short, transcript);
|
|
216
|
+
socialPosts.push(...posts);
|
|
217
|
+
}
|
|
218
|
+
}, stageResults);
|
|
219
|
+
}
|
|
220
|
+
// 12. Medium Clip Posts — generate social posts per medium clip
|
|
221
|
+
if (transcript && mediumClips.length > 0 && !cfg.SKIP_SOCIAL) {
|
|
222
|
+
await runStage(Stage.MediumClipPosts, async () => {
|
|
223
|
+
for (const clip of mediumClips) {
|
|
224
|
+
const asShortClip = {
|
|
225
|
+
id: clip.id,
|
|
226
|
+
title: clip.title,
|
|
227
|
+
slug: clip.slug,
|
|
228
|
+
segments: clip.segments,
|
|
229
|
+
totalDuration: clip.totalDuration,
|
|
230
|
+
outputPath: clip.outputPath,
|
|
231
|
+
captionedPath: clip.captionedPath,
|
|
232
|
+
description: clip.description,
|
|
233
|
+
tags: clip.tags,
|
|
234
|
+
};
|
|
235
|
+
const posts = await generateShortPosts(video, asShortClip, transcript);
|
|
236
|
+
// Move posts to medium-clips/{slug}/posts/
|
|
237
|
+
const clipsDir = path.join(path.dirname(video.repoPath), 'medium-clips');
|
|
238
|
+
const postsDir = path.join(clipsDir, clip.slug, 'posts');
|
|
239
|
+
await fs.mkdir(postsDir, { recursive: true });
|
|
240
|
+
for (const post of posts) {
|
|
241
|
+
const destPath = path.join(postsDir, path.basename(post.outputPath));
|
|
242
|
+
await fs.copyFile(post.outputPath, destPath);
|
|
243
|
+
await fs.unlink(post.outputPath).catch(() => { });
|
|
244
|
+
post.outputPath = destPath;
|
|
245
|
+
}
|
|
246
|
+
socialPosts.push(...posts);
|
|
247
|
+
}
|
|
248
|
+
}, stageResults);
|
|
249
|
+
}
|
|
250
|
+
// 13. Blog Post
|
|
251
|
+
let blogPost;
|
|
252
|
+
if (transcript && summary) {
|
|
253
|
+
blogPost = await runStage(Stage.Blog, () => generateBlogPost(video, transcript, summary), stageResults);
|
|
254
|
+
}
|
|
255
|
+
// 14. Git
|
|
256
|
+
if (!cfg.SKIP_GIT) {
|
|
257
|
+
await runStage(Stage.GitPush, () => commitAndPush(video.slug), stageResults);
|
|
258
|
+
}
|
|
259
|
+
const totalDuration = Date.now() - pipelineStart;
|
|
260
|
+
logger.info(`Pipeline completed in ${totalDuration}ms`);
|
|
261
|
+
return {
|
|
262
|
+
video,
|
|
263
|
+
transcript,
|
|
264
|
+
editedVideoPath,
|
|
265
|
+
captions,
|
|
266
|
+
captionedVideoPath,
|
|
267
|
+
summary,
|
|
268
|
+
chapters,
|
|
269
|
+
shorts,
|
|
270
|
+
mediumClips,
|
|
271
|
+
socialPosts,
|
|
272
|
+
blogPost,
|
|
273
|
+
stageResults,
|
|
274
|
+
totalDuration,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
export async function processVideoSafe(videoPath) {
|
|
278
|
+
try {
|
|
279
|
+
return await processVideo(videoPath);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
283
|
+
logger.error(`Pipeline failed with uncaught error: ${message}`);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { VideoFile, Transcript } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Generate SRT, VTT, and ASS caption files for a video and save them to disk.
|
|
4
|
+
* Returns the list of written file paths.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateCaptions(video: VideoFile, transcript: Transcript): Promise<string[]>;
|
|
7
|
+
//# sourceMappingURL=captionGeneration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captionGeneration.d.ts","sourceRoot":"","sources":["../../src/services/captionGeneration.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAKhD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,EAChB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CAsBnB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fsp from 'fs/promises';
|
|
3
|
+
import { generateSRT, generateVTT, generateStyledASS } from '../tools/captions/captionGenerator';
|
|
4
|
+
import { getConfig } from '../config/environment';
|
|
5
|
+
import logger from '../config/logger';
|
|
6
|
+
/**
|
|
7
|
+
* Generate SRT, VTT, and ASS caption files for a video and save them to disk.
|
|
8
|
+
* Returns the list of written file paths.
|
|
9
|
+
*/
|
|
10
|
+
export async function generateCaptions(video, transcript) {
|
|
11
|
+
const config = getConfig();
|
|
12
|
+
const captionsDir = path.join(config.OUTPUT_DIR, video.slug, 'captions');
|
|
13
|
+
await fsp.mkdir(captionsDir, { recursive: true });
|
|
14
|
+
const srtPath = path.join(captionsDir, 'captions.srt');
|
|
15
|
+
const vttPath = path.join(captionsDir, 'captions.vtt');
|
|
16
|
+
const assPath = path.join(captionsDir, 'captions.ass');
|
|
17
|
+
const srt = generateSRT(transcript);
|
|
18
|
+
const vtt = generateVTT(transcript);
|
|
19
|
+
const ass = generateStyledASS(transcript);
|
|
20
|
+
await Promise.all([
|
|
21
|
+
fsp.writeFile(srtPath, srt, 'utf-8'),
|
|
22
|
+
fsp.writeFile(vttPath, vtt, 'utf-8'),
|
|
23
|
+
fsp.writeFile(assPath, ass, 'utf-8'),
|
|
24
|
+
]);
|
|
25
|
+
const paths = [srtPath, vttPath, assPath];
|
|
26
|
+
logger.info(`Captions saved: ${paths.join(', ')}`);
|
|
27
|
+
return paths;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=captionGeneration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captionGeneration.js","sourceRoot":"","sources":["../../src/services/captionGeneration.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAA;AAChG,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,MAAM,MAAM,kBAAkB,CAAA;AAErC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAgB,EAChB,UAAsB;IAEtB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACxE,MAAM,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEjD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;IAEtD,MAAM,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAA;IAEzC,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC;QACpC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC;QACpC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC;KACrC,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACzC,MAAM,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClD,OAAO,KAAK,CAAA;AACd,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface FileWatcherOptions {
|
|
3
|
+
processExisting?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class FileWatcher extends EventEmitter {
|
|
6
|
+
private watchFolder;
|
|
7
|
+
private watcher;
|
|
8
|
+
private processExisting;
|
|
9
|
+
constructor(options?: FileWatcherOptions);
|
|
10
|
+
private static readonly MIN_FILE_SIZE;
|
|
11
|
+
private static readonly EXTRA_STABILITY_DELAY;
|
|
12
|
+
/** Read file size, wait, read again — if it changed the file is still being written. */
|
|
13
|
+
private isFileStable;
|
|
14
|
+
private handleDetectedFile;
|
|
15
|
+
private scanExistingFiles;
|
|
16
|
+
start(): void;
|
|
17
|
+
stop(): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=fileWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileWatcher.d.ts","sourceRoot":"","sources":["../../src/services/fileWatcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAKrC,MAAM,WAAW,kBAAkB;IACjC,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,eAAe,CAAS;gBAEpB,OAAO,GAAE,kBAAuB;IAY5C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAc;IACnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAO;IAEpD,wFAAwF;YAC1E,YAAY;YAWZ,kBAAkB;IA8BhC,OAAO,CAAC,iBAAiB;IAYzB,KAAK,IAAI,IAAI;IAqDb,IAAI,IAAI,IAAI;CAOb"}
|