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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { watch } from 'chokidar';
|
|
2
|
+
import { getConfig } from '../config/environment';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import logger from '../config/logger';
|
|
7
|
+
export class FileWatcher extends EventEmitter {
|
|
8
|
+
watchFolder;
|
|
9
|
+
watcher = null;
|
|
10
|
+
processExisting;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
super();
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
this.watchFolder = config.WATCH_FOLDER;
|
|
15
|
+
this.processExisting = options.processExisting ?? false;
|
|
16
|
+
if (!fs.existsSync(this.watchFolder)) {
|
|
17
|
+
fs.mkdirSync(this.watchFolder, { recursive: true });
|
|
18
|
+
logger.info(`Created watch folder: ${this.watchFolder}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
static MIN_FILE_SIZE = 1024 * 1024; // 1MB
|
|
22
|
+
static EXTRA_STABILITY_DELAY = 3000;
|
|
23
|
+
/** Read file size, wait, read again — if it changed the file is still being written. */
|
|
24
|
+
async isFileStable(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
const sizeBefore = fs.statSync(filePath).size;
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, FileWatcher.EXTRA_STABILITY_DELAY));
|
|
28
|
+
const sizeAfter = fs.statSync(filePath).size;
|
|
29
|
+
return sizeBefore === sizeAfter;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async handleDetectedFile(filePath) {
|
|
36
|
+
if (path.extname(filePath).toLowerCase() !== '.mp4') {
|
|
37
|
+
logger.debug(`[watcher] Ignoring non-mp4 file: ${filePath}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let fileSize;
|
|
41
|
+
try {
|
|
42
|
+
fileSize = fs.statSync(filePath).size;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
logger.warn(`[watcher] Could not stat file (may have been removed): ${filePath}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
logger.debug(`[watcher] File size: ${(fileSize / 1024 / 1024).toFixed(1)} MB — ${filePath}`);
|
|
49
|
+
if (fileSize < FileWatcher.MIN_FILE_SIZE) {
|
|
50
|
+
logger.warn(`Skipping small file (${fileSize} bytes), likely a failed recording: ${filePath}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const stable = await this.isFileStable(filePath);
|
|
54
|
+
if (!stable) {
|
|
55
|
+
logger.warn(`File is still being written, skipping for now: ${filePath}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
logger.info(`New video detected: ${filePath}`);
|
|
59
|
+
this.emit('new-video', filePath);
|
|
60
|
+
}
|
|
61
|
+
scanExistingFiles() {
|
|
62
|
+
const files = fs.readdirSync(this.watchFolder);
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
if (path.extname(file).toLowerCase() === '.mp4') {
|
|
65
|
+
const filePath = path.join(this.watchFolder, file);
|
|
66
|
+
this.handleDetectedFile(filePath).catch(err => logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
start() {
|
|
71
|
+
this.watcher = watch(this.watchFolder, {
|
|
72
|
+
persistent: true,
|
|
73
|
+
ignoreInitial: true,
|
|
74
|
+
depth: 0,
|
|
75
|
+
atomic: 100,
|
|
76
|
+
// Polling is more reliable on Windows for detecting renames (e.g. Bandicam temp→final)
|
|
77
|
+
usePolling: true,
|
|
78
|
+
interval: 500,
|
|
79
|
+
awaitWriteFinish: {
|
|
80
|
+
stabilityThreshold: 3000,
|
|
81
|
+
pollInterval: 200,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
this.watcher.on('add', (filePath) => {
|
|
85
|
+
logger.debug(`[watcher] 'add' event: ${filePath}`);
|
|
86
|
+
this.handleDetectedFile(filePath).catch(err => logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`));
|
|
87
|
+
});
|
|
88
|
+
this.watcher.on('change', (filePath) => {
|
|
89
|
+
logger.debug(`[watcher] 'change' event: ${filePath}`);
|
|
90
|
+
if (path.extname(filePath).toLowerCase() !== '.mp4')
|
|
91
|
+
return;
|
|
92
|
+
logger.info(`Change detected on video file: ${filePath}`);
|
|
93
|
+
this.handleDetectedFile(filePath).catch(err => logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`));
|
|
94
|
+
});
|
|
95
|
+
this.watcher.on('unlink', (filePath) => {
|
|
96
|
+
logger.debug(`[watcher] 'unlink' event: ${filePath}`);
|
|
97
|
+
});
|
|
98
|
+
this.watcher.on('raw', (event, rawPath, details) => {
|
|
99
|
+
logger.debug(`[watcher] raw event=${event} path=${rawPath}`);
|
|
100
|
+
});
|
|
101
|
+
this.watcher.on('error', (error) => {
|
|
102
|
+
logger.error(`File watcher error: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
});
|
|
104
|
+
this.watcher.on('ready', () => {
|
|
105
|
+
logger.info('File watcher is fully initialized and ready');
|
|
106
|
+
if (this.processExisting) {
|
|
107
|
+
this.scanExistingFiles();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
logger.info(`Watching for new .mp4 files in: ${this.watchFolder}`);
|
|
111
|
+
}
|
|
112
|
+
stop() {
|
|
113
|
+
if (this.watcher) {
|
|
114
|
+
this.watcher.close();
|
|
115
|
+
this.watcher = null;
|
|
116
|
+
logger.info('File watcher stopped');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=fileWatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileWatcher.js","sourceRoot":"","sources":["../../src/services/fileWatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitOperations.d.ts","sourceRoot":"","sources":["../../src/services/gitOperations.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BtF;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAalE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { getConfig } from '../config/environment';
|
|
3
|
+
import logger from '../config/logger';
|
|
4
|
+
export async function commitAndPush(videoSlug, message) {
|
|
5
|
+
const { REPO_ROOT } = getConfig();
|
|
6
|
+
const commitMessage = message || `Auto-processed video: ${videoSlug}`;
|
|
7
|
+
try {
|
|
8
|
+
logger.info(`Staging all changes in ${REPO_ROOT}`);
|
|
9
|
+
execSync('git add -A', { cwd: REPO_ROOT, stdio: 'pipe' });
|
|
10
|
+
logger.info(`Committing: ${commitMessage}`);
|
|
11
|
+
execSync(`git commit -m "${commitMessage}"`, { cwd: REPO_ROOT, stdio: 'pipe' });
|
|
12
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT, stdio: 'pipe' })
|
|
13
|
+
.toString()
|
|
14
|
+
.trim();
|
|
15
|
+
logger.info(`Pushing to origin ${branch}`);
|
|
16
|
+
execSync(`git push origin ${branch}`, { cwd: REPO_ROOT, stdio: 'pipe' });
|
|
17
|
+
logger.info('Git commit and push completed successfully');
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
21
|
+
if (msg.includes('nothing to commit')) {
|
|
22
|
+
logger.info('Nothing to commit, working tree clean');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
logger.error(`Git operation failed: ${msg}`);
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function stageFiles(patterns) {
|
|
30
|
+
const { REPO_ROOT } = getConfig();
|
|
31
|
+
for (const pattern of patterns) {
|
|
32
|
+
try {
|
|
33
|
+
logger.info(`Staging files matching: ${pattern}`);
|
|
34
|
+
execSync(`git add ${pattern}`, { cwd: REPO_ROOT, stdio: 'pipe' });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
38
|
+
logger.error(`Failed to stage pattern "${pattern}": ${msg}`);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=gitOperations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitOperations.js","sourceRoot":"","sources":["../../src/services/gitOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,MAAM,MAAM,kBAAkB,CAAA;AAErC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB,EAAE,OAAgB;IACrE,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAAA;IACjC,MAAM,aAAa,GAAG,OAAO,IAAI,yBAAyB,SAAS,EAAE,CAAA;IAErE,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,0BAA0B,SAAS,EAAE,CAAC,CAAA;QAClD,QAAQ,CAAC,YAAY,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAEzD,MAAM,CAAC,IAAI,CAAC,eAAe,aAAa,EAAE,CAAC,CAAA;QAC3C,QAAQ,CAAC,kBAAkB,aAAa,GAAG,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAE/E,MAAM,MAAM,GAAG,QAAQ,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;aAC1F,QAAQ,EAAE;aACV,IAAI,EAAE,CAAA;QACT,MAAM,CAAC,IAAI,CAAC,qBAAqB,MAAM,EAAE,CAAC,CAAA;QAC1C,QAAQ,CAAC,mBAAmB,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAExE,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAA;IAC3D,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAClE,IAAI,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAA;YACpD,OAAM;QACR,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,EAAE,CAAC,CAAA;QAC5C,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAkB;IACjD,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAAA;IAEjC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAA;YACjD,QAAQ,CAAC,WAAW,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACnE,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClE,MAAM,CAAC,KAAK,CAAC,4BAA4B,OAAO,MAAM,GAAG,EAAE,CAAC,CAAA;YAC5D,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Platform, SocialPost } from '../types';
|
|
2
|
+
export interface SocialPlatformClient {
|
|
3
|
+
post(content: SocialPost): Promise<{
|
|
4
|
+
success: boolean;
|
|
5
|
+
url?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}>;
|
|
8
|
+
validate(content: SocialPost): boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Placeholder client used until real platform API integrations are wired up.
|
|
12
|
+
* Each platform will eventually get its own client class.
|
|
13
|
+
*/
|
|
14
|
+
export declare class PlaceholderPlatformClient implements SocialPlatformClient {
|
|
15
|
+
private readonly platform;
|
|
16
|
+
constructor(platform: Platform);
|
|
17
|
+
post(content: SocialPost): Promise<{
|
|
18
|
+
success: boolean;
|
|
19
|
+
url?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}>;
|
|
22
|
+
validate(_content: SocialPost): boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the appropriate SocialPlatformClient for a given platform.
|
|
26
|
+
* Currently returns a PlaceholderPlatformClient for all platforms.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getPlatformClient(platform: Platform): SocialPlatformClient;
|
|
29
|
+
/**
|
|
30
|
+
* Publishes an array of SocialPost items to their respective platforms.
|
|
31
|
+
* Returns a map of platform → result for each post.
|
|
32
|
+
*/
|
|
33
|
+
export declare function publishToAllPlatforms(posts: SocialPost[]): Promise<Map<Platform, {
|
|
34
|
+
success: boolean;
|
|
35
|
+
url?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
}>>;
|
|
38
|
+
//# sourceMappingURL=socialPosting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socialPosting.d.ts","sourceRoot":"","sources":["../../src/services/socialPosting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAO/C,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtF,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAA;CACvC;AAMD;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,oBAAoB;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,QAAQ;IAEzC,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAS5F,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO;CAWxC;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,oBAAoB,CAyC1E;AAMD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,UAAU,EAAE,GAClB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAiB5E"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Platform } from '../types';
|
|
2
|
+
import logger from '../config/logger';
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// PLACEHOLDER CLIENT
|
|
5
|
+
// ============================================================================
|
|
6
|
+
/**
|
|
7
|
+
* Placeholder client used until real platform API integrations are wired up.
|
|
8
|
+
* Each platform will eventually get its own client class.
|
|
9
|
+
*/
|
|
10
|
+
export class PlaceholderPlatformClient {
|
|
11
|
+
platform;
|
|
12
|
+
constructor(platform) {
|
|
13
|
+
this.platform = platform;
|
|
14
|
+
}
|
|
15
|
+
async post(content) {
|
|
16
|
+
logger.info(`Placeholder: Would post to ${this.platform}`, {
|
|
17
|
+
platform: this.platform,
|
|
18
|
+
contentLength: content.content.length,
|
|
19
|
+
hashtags: content.hashtags,
|
|
20
|
+
});
|
|
21
|
+
return { success: true };
|
|
22
|
+
}
|
|
23
|
+
validate(_content) {
|
|
24
|
+
// TODO: Implement platform-specific validation
|
|
25
|
+
// Each platform has different limits:
|
|
26
|
+
// - TikTok: 2200 char caption, video 15s-10min
|
|
27
|
+
// - YouTube: 5000 char description, shorts ≤60s
|
|
28
|
+
// - Instagram: 2200 char caption, reels ≤90s
|
|
29
|
+
// - LinkedIn: 3000 char post, video ≤10min
|
|
30
|
+
// - X (Twitter): 280 char tweet, video ≤140s
|
|
31
|
+
logger.warn(`[${this.platform}] Content validation not yet implemented — accepting all content`);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// PLATFORM CLIENT FACTORY
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Returns the appropriate SocialPlatformClient for a given platform.
|
|
40
|
+
* Currently returns a PlaceholderPlatformClient for all platforms.
|
|
41
|
+
*/
|
|
42
|
+
export function getPlatformClient(platform) {
|
|
43
|
+
switch (platform) {
|
|
44
|
+
case Platform.TikTok:
|
|
45
|
+
// TODO: Replace with TikTokClient once TikTok API integration is implemented
|
|
46
|
+
// Expected: TikTok Content Posting API (OAuth 2.0, video upload via URL or file)
|
|
47
|
+
// Docs: https://developers.tiktok.com/doc/content-posting-api-get-started
|
|
48
|
+
logger.warn('[TikTok] Using placeholder client — TikTok Content Posting API not yet integrated');
|
|
49
|
+
return new PlaceholderPlatformClient(platform);
|
|
50
|
+
case Platform.YouTube:
|
|
51
|
+
// TODO: Replace with YouTubeClient once YouTube Data API integration is implemented
|
|
52
|
+
// Expected: YouTube Data API v3 (OAuth 2.0, videos.insert for Shorts upload)
|
|
53
|
+
// Docs: https://developers.google.com/youtube/v3/docs/videos/insert
|
|
54
|
+
logger.warn('[YouTube] Using placeholder client — YouTube Data API v3 not yet integrated');
|
|
55
|
+
return new PlaceholderPlatformClient(platform);
|
|
56
|
+
case Platform.Instagram:
|
|
57
|
+
// TODO: Replace with InstagramClient once Instagram Graph API integration is implemented
|
|
58
|
+
// Expected: Instagram Graph API (OAuth 2.0, Reels publishing via container + publish)
|
|
59
|
+
// Docs: https://developers.facebook.com/docs/instagram-api/guides/content-publishing
|
|
60
|
+
logger.warn('[Instagram] Using placeholder client — Instagram Graph API not yet integrated');
|
|
61
|
+
return new PlaceholderPlatformClient(platform);
|
|
62
|
+
case Platform.LinkedIn:
|
|
63
|
+
// TODO: Replace with LinkedInClient once LinkedIn API integration is implemented
|
|
64
|
+
// Expected: LinkedIn Marketing API (OAuth 2.0, ugcPosts for video + text)
|
|
65
|
+
// Docs: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares
|
|
66
|
+
logger.warn('[LinkedIn] Using placeholder client — LinkedIn Marketing API not yet integrated');
|
|
67
|
+
return new PlaceholderPlatformClient(platform);
|
|
68
|
+
case Platform.X:
|
|
69
|
+
// TODO: Replace with XClient once X (Twitter) API v2 integration is implemented
|
|
70
|
+
// Expected: X API v2 (OAuth 2.0, media upload + tweet creation)
|
|
71
|
+
// Docs: https://developer.x.com/en/docs/x-api/tweets/manage-tweets
|
|
72
|
+
logger.warn('[X] Using placeholder client — X API v2 not yet integrated');
|
|
73
|
+
return new PlaceholderPlatformClient(platform);
|
|
74
|
+
default:
|
|
75
|
+
logger.warn(`Unknown platform: ${platform}, using placeholder client`);
|
|
76
|
+
return new PlaceholderPlatformClient(platform);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// PUBLISH TO ALL PLATFORMS
|
|
81
|
+
// ============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Publishes an array of SocialPost items to their respective platforms.
|
|
84
|
+
* Returns a map of platform → result for each post.
|
|
85
|
+
*/
|
|
86
|
+
export async function publishToAllPlatforms(posts) {
|
|
87
|
+
const results = new Map();
|
|
88
|
+
for (const post of posts) {
|
|
89
|
+
const client = getPlatformClient(post.platform);
|
|
90
|
+
try {
|
|
91
|
+
const result = await client.post(post);
|
|
92
|
+
results.set(post.platform, result);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
96
|
+
logger.error(`Failed to publish to ${post.platform}`, { error: errorMessage });
|
|
97
|
+
results.set(post.platform, { success: false, error: errorMessage });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=socialPosting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socialPosting.js","sourceRoot":"","sources":["../../src/services/socialPosting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAC/C,OAAO,MAAM,MAAM,kBAAkB,CAAA;AAWrC,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,OAAO,yBAAyB;IACP;IAA7B,YAA6B,QAAkB;QAAlB,aAAQ,GAAR,QAAQ,CAAU;IAAG,CAAC;IAEnD,KAAK,CAAC,IAAI,CAAC,OAAmB;QAC5B,MAAM,CAAC,IAAI,CAAC,8BAA8B,IAAI,CAAC,QAAQ,EAAE,EAAE;YACzD,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM;YACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC,CAAA;QACF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC1B,CAAC;IAED,QAAQ,CAAC,QAAoB;QAC3B,+CAA+C;QAC/C,sCAAsC;QACtC,iDAAiD;QACjD,kDAAkD;QAClD,+CAA+C;QAC/C,6CAA6C;QAC7C,+CAA+C;QAC/C,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,kEAAkE,CAAC,CAAA;QAChG,OAAO,IAAI,CAAA;IACb,CAAC;CACF;AAED,+EAA+E;AAC/E,0BAA0B;AAC1B,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IAClD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ,CAAC,MAAM;YAClB,6EAA6E;YAC7E,iFAAiF;YACjF,0EAA0E;YAC1E,MAAM,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAA;YAChG,OAAO,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAA;QAEhD,KAAK,QAAQ,CAAC,OAAO;YACnB,oFAAoF;YACpF,6EAA6E;YAC7E,oEAAoE;YACpE,MAAM,CAAC,IAAI,CAAC,6EAA6E,CAAC,CAAA;YAC1F,OAAO,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAA;QAEhD,KAAK,QAAQ,CAAC,SAAS;YACrB,yFAAyF;YACzF,sFAAsF;YACtF,qFAAqF;YACrF,MAAM,CAAC,IAAI,CAAC,+EAA+E,CAAC,CAAA;YAC5F,OAAO,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAA;QAEhD,KAAK,QAAQ,CAAC,QAAQ;YACpB,iFAAiF;YACjF,0EAA0E;YAC1E,yFAAyF;YACzF,MAAM,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAA;YAC9F,OAAO,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAA;QAEhD,KAAK,QAAQ,CAAC,CAAC;YACb,gFAAgF;YAChF,gEAAgE;YAChE,mEAAmE;YACnE,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAA;YACzE,OAAO,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAA;QAEhD;YACE,MAAM,CAAC,IAAI,CAAC,qBAAqB,QAAQ,4BAA4B,CAAC,CAAA;YACtE,OAAO,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAA;IAClD,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAAmB;IAEnB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgE,CAAA;IAEvF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAE/C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACrE,MAAM,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;YAC9E,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;QACrE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcription.d.ts","sourceRoot":"","sources":["../../src/services/transcription.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAA;AAM/D,wBAAsB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAuD3E"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fsp from 'fs/promises';
|
|
3
|
+
import { extractAudio, splitAudioIntoChunks } from '../tools/ffmpeg/audioExtraction';
|
|
4
|
+
import { transcribeAudio } from '../tools/whisper/whisperClient';
|
|
5
|
+
import { getConfig } from '../config/environment';
|
|
6
|
+
import logger from '../config/logger';
|
|
7
|
+
const MAX_WHISPER_SIZE_MB = 25;
|
|
8
|
+
export async function transcribeVideo(video) {
|
|
9
|
+
const config = getConfig();
|
|
10
|
+
// 1. Create cache directory for temp audio files
|
|
11
|
+
const cacheDir = path.join(config.REPO_ROOT, 'cache');
|
|
12
|
+
await fsp.mkdir(cacheDir, { recursive: true });
|
|
13
|
+
logger.info(`Cache directory ready: ${cacheDir}`);
|
|
14
|
+
// 2. Extract audio as compressed MP3 (much smaller than WAV)
|
|
15
|
+
const mp3Path = path.join(cacheDir, `${video.slug}.mp3`);
|
|
16
|
+
logger.info(`Extracting audio for "${video.slug}"`);
|
|
17
|
+
await extractAudio(video.repoPath, mp3Path);
|
|
18
|
+
// 3. Check file size and chunk if necessary
|
|
19
|
+
const stats = await fsp.stat(mp3Path);
|
|
20
|
+
const fileSizeMB = stats.size / (1024 * 1024);
|
|
21
|
+
logger.info(`Extracted audio: ${fileSizeMB.toFixed(1)}MB`);
|
|
22
|
+
let transcript;
|
|
23
|
+
if (fileSizeMB <= MAX_WHISPER_SIZE_MB) {
|
|
24
|
+
// Single-file transcription
|
|
25
|
+
logger.info(`Transcribing audio for "${video.slug}"`);
|
|
26
|
+
transcript = await transcribeAudio(mp3Path);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Chunk and transcribe (very long videos, 50+ min)
|
|
30
|
+
logger.info(`Audio exceeds ${MAX_WHISPER_SIZE_MB}MB, splitting into chunks`);
|
|
31
|
+
const chunkPaths = await splitAudioIntoChunks(mp3Path);
|
|
32
|
+
transcript = await transcribeChunks(chunkPaths);
|
|
33
|
+
// Clean up chunk files
|
|
34
|
+
for (const chunkPath of chunkPaths) {
|
|
35
|
+
if (chunkPath !== mp3Path) {
|
|
36
|
+
await fsp.unlink(chunkPath).catch(() => { });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// 4. Save transcript JSON
|
|
41
|
+
const transcriptDir = path.join(config.OUTPUT_DIR, video.slug);
|
|
42
|
+
await fsp.mkdir(transcriptDir, { recursive: true });
|
|
43
|
+
const transcriptPath = path.join(transcriptDir, 'transcript.json');
|
|
44
|
+
await fsp.writeFile(transcriptPath, JSON.stringify(transcript, null, 2), 'utf-8');
|
|
45
|
+
logger.info(`Transcript saved: ${transcriptPath}`);
|
|
46
|
+
// 5. Clean up temp audio file
|
|
47
|
+
await fsp.unlink(mp3Path).catch(() => { });
|
|
48
|
+
logger.info(`Cleaned up temp file: ${mp3Path}`);
|
|
49
|
+
// 6. Return the transcript
|
|
50
|
+
logger.info(`Transcription complete for "${video.slug}" — ` +
|
|
51
|
+
`${transcript.segments.length} segments, ${transcript.words.length} words`);
|
|
52
|
+
return transcript;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Transcribe multiple audio chunks and merge results with adjusted timestamps.
|
|
56
|
+
*/
|
|
57
|
+
async function transcribeChunks(chunkPaths) {
|
|
58
|
+
let allText = '';
|
|
59
|
+
const allSegments = [];
|
|
60
|
+
const allWords = [];
|
|
61
|
+
let cumulativeOffset = 0;
|
|
62
|
+
let totalDuration = 0;
|
|
63
|
+
let language = 'unknown';
|
|
64
|
+
for (let i = 0; i < chunkPaths.length; i++) {
|
|
65
|
+
logger.info(`Transcribing chunk ${i + 1}/${chunkPaths.length}: ${chunkPaths[i]}`);
|
|
66
|
+
const result = await transcribeAudio(chunkPaths[i]);
|
|
67
|
+
if (i === 0)
|
|
68
|
+
language = result.language;
|
|
69
|
+
// Adjust timestamps by cumulative offset
|
|
70
|
+
const offsetSegments = result.segments.map((s) => ({
|
|
71
|
+
...s,
|
|
72
|
+
id: allSegments.length + s.id,
|
|
73
|
+
start: s.start + cumulativeOffset,
|
|
74
|
+
end: s.end + cumulativeOffset,
|
|
75
|
+
words: s.words.map((w) => ({
|
|
76
|
+
...w,
|
|
77
|
+
start: w.start + cumulativeOffset,
|
|
78
|
+
end: w.end + cumulativeOffset,
|
|
79
|
+
})),
|
|
80
|
+
}));
|
|
81
|
+
const offsetWords = result.words.map((w) => ({
|
|
82
|
+
...w,
|
|
83
|
+
start: w.start + cumulativeOffset,
|
|
84
|
+
end: w.end + cumulativeOffset,
|
|
85
|
+
}));
|
|
86
|
+
allText += (allText ? ' ' : '') + result.text;
|
|
87
|
+
allSegments.push(...offsetSegments);
|
|
88
|
+
allWords.push(...offsetWords);
|
|
89
|
+
cumulativeOffset += result.duration;
|
|
90
|
+
totalDuration += result.duration;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
text: allText,
|
|
94
|
+
segments: allSegments,
|
|
95
|
+
words: allWords,
|
|
96
|
+
language,
|
|
97
|
+
duration: totalDuration,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=transcription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcription.js","sourceRoot":"","sources":["../../src/services/transcription.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"videoIngestion.d.ts","sourceRoot":"","sources":["../../src/services/videoIngestion.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAoBpC,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CA2FxE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import fsp from 'fs/promises';
|
|
4
|
+
import slugify from 'slugify';
|
|
5
|
+
import ffmpeg from 'fluent-ffmpeg';
|
|
6
|
+
import { getConfig } from '../config/environment';
|
|
7
|
+
import logger from '../config/logger';
|
|
8
|
+
const ffmpegBin = process.env.FFMPEG_PATH || 'ffmpeg';
|
|
9
|
+
const ffprobeBin = process.env.FFPROBE_PATH || 'ffprobe';
|
|
10
|
+
ffmpeg.setFfmpegPath(ffmpegBin);
|
|
11
|
+
ffmpeg.setFfprobePath(ffprobeBin);
|
|
12
|
+
function getVideoMetadata(filePath) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
return reject(err);
|
|
17
|
+
}
|
|
18
|
+
resolve({ duration: metadata.format.duration ?? 0 });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export async function ingestVideo(sourcePath) {
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
const baseName = path.basename(sourcePath, path.extname(sourcePath));
|
|
25
|
+
const slug = slugify(baseName, { lower: true });
|
|
26
|
+
const recordingsDir = path.join(config.OUTPUT_DIR, slug);
|
|
27
|
+
const thumbnailsDir = path.join(recordingsDir, 'thumbnails');
|
|
28
|
+
const shortsDir = path.join(recordingsDir, 'shorts');
|
|
29
|
+
const socialPostsDir = path.join(recordingsDir, 'social-posts');
|
|
30
|
+
logger.info(`Ingesting video: ${sourcePath} → ${slug}`);
|
|
31
|
+
// Clean stale artifacts if output folder already exists
|
|
32
|
+
if (fs.existsSync(recordingsDir)) {
|
|
33
|
+
logger.warn(`Output folder already exists, cleaning previous artifacts: ${recordingsDir}`);
|
|
34
|
+
const subDirs = ['thumbnails', 'shorts', 'social-posts', 'chapters', 'mediums'];
|
|
35
|
+
for (const sub of subDirs) {
|
|
36
|
+
await fsp.rm(path.join(recordingsDir, sub), { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
const stalePatterns = [
|
|
39
|
+
'transcript.json', 'transcript-edited.json',
|
|
40
|
+
'captions.srt', 'captions.vtt', 'captions.ass',
|
|
41
|
+
'summary.md', 'blog-post.md', 'README.md',
|
|
42
|
+
];
|
|
43
|
+
for (const pattern of stalePatterns) {
|
|
44
|
+
await fsp.rm(path.join(recordingsDir, pattern), { force: true });
|
|
45
|
+
}
|
|
46
|
+
const files = await fsp.readdir(recordingsDir);
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
if (file.endsWith('-edited.mp4') || file.endsWith('-captioned.mp4')) {
|
|
49
|
+
await fsp.rm(path.join(recordingsDir, file), { force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
await fsp.mkdir(recordingsDir, { recursive: true });
|
|
54
|
+
await fsp.mkdir(thumbnailsDir, { recursive: true });
|
|
55
|
+
await fsp.mkdir(shortsDir, { recursive: true });
|
|
56
|
+
await fsp.mkdir(socialPostsDir, { recursive: true });
|
|
57
|
+
const destFilename = `${slug}.mp4`;
|
|
58
|
+
const destPath = path.join(recordingsDir, destFilename);
|
|
59
|
+
let needsCopy = true;
|
|
60
|
+
try {
|
|
61
|
+
const destStats = await fsp.stat(destPath);
|
|
62
|
+
const srcStats = await fsp.stat(sourcePath);
|
|
63
|
+
if (destStats.size === srcStats.size) {
|
|
64
|
+
logger.info(`Video already copied (same size), skipping copy`);
|
|
65
|
+
needsCopy = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Dest doesn't exist, need to copy
|
|
70
|
+
}
|
|
71
|
+
if (needsCopy) {
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
const readStream = fs.createReadStream(sourcePath);
|
|
74
|
+
const writeStream = fs.createWriteStream(destPath);
|
|
75
|
+
readStream.on('error', reject);
|
|
76
|
+
writeStream.on('error', reject);
|
|
77
|
+
writeStream.on('finish', resolve);
|
|
78
|
+
readStream.pipe(writeStream);
|
|
79
|
+
});
|
|
80
|
+
logger.info(`Copied video to ${destPath}`);
|
|
81
|
+
}
|
|
82
|
+
let duration = 0;
|
|
83
|
+
try {
|
|
84
|
+
const meta = await getVideoMetadata(destPath);
|
|
85
|
+
duration = meta.duration;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
logger.warn(`ffprobe failed, continuing without duration metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
89
|
+
}
|
|
90
|
+
const stats = await fsp.stat(destPath);
|
|
91
|
+
logger.info(`Video metadata: duration=${duration}s, size=${stats.size} bytes`);
|
|
92
|
+
return {
|
|
93
|
+
originalPath: sourcePath,
|
|
94
|
+
repoPath: destPath,
|
|
95
|
+
videoDir: recordingsDir,
|
|
96
|
+
slug,
|
|
97
|
+
filename: destFilename,
|
|
98
|
+
duration,
|
|
99
|
+
size: stats.size,
|
|
100
|
+
createdAt: new Date(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=videoIngestion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"videoIngestion.js","sourceRoot":"","sources":["../../src/services/videoIngestion.ts"],"names":[],"mappings":"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"}
|