vidpipe 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +4766 -123
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -2
  5. package/dist/agents/BaseAgent.d.ts +0 -52
  6. package/dist/agents/BaseAgent.d.ts.map +0 -1
  7. package/dist/agents/BaseAgent.js +0 -102
  8. package/dist/agents/BaseAgent.js.map +0 -1
  9. package/dist/agents/BlogAgent.d.ts +0 -3
  10. package/dist/agents/BlogAgent.d.ts.map +0 -1
  11. package/dist/agents/BlogAgent.js +0 -163
  12. package/dist/agents/BlogAgent.js.map +0 -1
  13. package/dist/agents/ChapterAgent.d.ts +0 -11
  14. package/dist/agents/ChapterAgent.d.ts.map +0 -1
  15. package/dist/agents/ChapterAgent.js +0 -191
  16. package/dist/agents/ChapterAgent.js.map +0 -1
  17. package/dist/agents/MediumVideoAgent.d.ts +0 -3
  18. package/dist/agents/MediumVideoAgent.d.ts.map +0 -1
  19. package/dist/agents/MediumVideoAgent.js +0 -219
  20. package/dist/agents/MediumVideoAgent.js.map +0 -1
  21. package/dist/agents/ShortsAgent.d.ts +0 -3
  22. package/dist/agents/ShortsAgent.d.ts.map +0 -1
  23. package/dist/agents/ShortsAgent.js +0 -243
  24. package/dist/agents/ShortsAgent.js.map +0 -1
  25. package/dist/agents/SilenceRemovalAgent.d.ts +0 -9
  26. package/dist/agents/SilenceRemovalAgent.d.ts.map +0 -1
  27. package/dist/agents/SilenceRemovalAgent.js +0 -209
  28. package/dist/agents/SilenceRemovalAgent.js.map +0 -1
  29. package/dist/agents/SocialMediaAgent.d.ts +0 -4
  30. package/dist/agents/SocialMediaAgent.d.ts.map +0 -1
  31. package/dist/agents/SocialMediaAgent.js +0 -248
  32. package/dist/agents/SocialMediaAgent.js.map +0 -1
  33. package/dist/agents/SummaryAgent.d.ts +0 -11
  34. package/dist/agents/SummaryAgent.d.ts.map +0 -1
  35. package/dist/agents/SummaryAgent.js +0 -333
  36. package/dist/agents/SummaryAgent.js.map +0 -1
  37. package/dist/commands/doctor.d.ts +0 -4
  38. package/dist/commands/doctor.d.ts.map +0 -1
  39. package/dist/commands/doctor.js +0 -230
  40. package/dist/commands/doctor.js.map +0 -1
  41. package/dist/config/brand.d.ts +0 -29
  42. package/dist/config/brand.d.ts.map +0 -1
  43. package/dist/config/brand.js +0 -83
  44. package/dist/config/brand.js.map +0 -1
  45. package/dist/config/environment.d.ts +0 -39
  46. package/dist/config/environment.d.ts.map +0 -1
  47. package/dist/config/environment.js +0 -47
  48. package/dist/config/environment.js.map +0 -1
  49. package/dist/config/ffmpegResolver.d.ts +0 -3
  50. package/dist/config/ffmpegResolver.d.ts.map +0 -1
  51. package/dist/config/ffmpegResolver.js +0 -37
  52. package/dist/config/ffmpegResolver.js.map +0 -1
  53. package/dist/config/logger.d.ts +0 -5
  54. package/dist/config/logger.d.ts.map +0 -1
  55. package/dist/config/logger.js +0 -13
  56. package/dist/config/logger.js.map +0 -1
  57. package/dist/config/pricing.d.ts +0 -34
  58. package/dist/config/pricing.d.ts.map +0 -1
  59. package/dist/config/pricing.js +0 -71
  60. package/dist/config/pricing.js.map +0 -1
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/pipeline.d.ts +0 -57
  63. package/dist/pipeline.d.ts.map +0 -1
  64. package/dist/pipeline.js +0 -324
  65. package/dist/pipeline.js.map +0 -1
  66. package/dist/providers/ClaudeProvider.d.ts +0 -14
  67. package/dist/providers/ClaudeProvider.d.ts.map +0 -1
  68. package/dist/providers/ClaudeProvider.js +0 -182
  69. package/dist/providers/ClaudeProvider.js.map +0 -1
  70. package/dist/providers/CopilotProvider.d.ts +0 -17
  71. package/dist/providers/CopilotProvider.d.ts.map +0 -1
  72. package/dist/providers/CopilotProvider.js +0 -149
  73. package/dist/providers/CopilotProvider.js.map +0 -1
  74. package/dist/providers/OpenAIProvider.d.ts +0 -14
  75. package/dist/providers/OpenAIProvider.d.ts.map +0 -1
  76. package/dist/providers/OpenAIProvider.js +0 -175
  77. package/dist/providers/OpenAIProvider.js.map +0 -1
  78. package/dist/providers/index.d.ts +0 -18
  79. package/dist/providers/index.d.ts.map +0 -1
  80. package/dist/providers/index.js +0 -61
  81. package/dist/providers/index.js.map +0 -1
  82. package/dist/providers/types.d.ts +0 -112
  83. package/dist/providers/types.d.ts.map +0 -1
  84. package/dist/providers/types.js +0 -8
  85. package/dist/providers/types.js.map +0 -1
  86. package/dist/services/captionGeneration.d.ts +0 -7
  87. package/dist/services/captionGeneration.d.ts.map +0 -1
  88. package/dist/services/captionGeneration.js +0 -29
  89. package/dist/services/captionGeneration.js.map +0 -1
  90. package/dist/services/costTracker.d.ts +0 -63
  91. package/dist/services/costTracker.d.ts.map +0 -1
  92. package/dist/services/costTracker.js +0 -137
  93. package/dist/services/costTracker.js.map +0 -1
  94. package/dist/services/fileWatcher.d.ts +0 -19
  95. package/dist/services/fileWatcher.d.ts.map +0 -1
  96. package/dist/services/fileWatcher.js +0 -120
  97. package/dist/services/fileWatcher.js.map +0 -1
  98. package/dist/services/gitOperations.d.ts +0 -3
  99. package/dist/services/gitOperations.d.ts.map +0 -1
  100. package/dist/services/gitOperations.js +0 -43
  101. package/dist/services/gitOperations.js.map +0 -1
  102. package/dist/services/socialPosting.d.ts +0 -38
  103. package/dist/services/socialPosting.d.ts.map +0 -1
  104. package/dist/services/socialPosting.js +0 -102
  105. package/dist/services/socialPosting.js.map +0 -1
  106. package/dist/services/transcription.d.ts +0 -3
  107. package/dist/services/transcription.d.ts.map +0 -1
  108. package/dist/services/transcription.js +0 -100
  109. package/dist/services/transcription.js.map +0 -1
  110. package/dist/services/videoIngestion.d.ts +0 -3
  111. package/dist/services/videoIngestion.d.ts.map +0 -1
  112. package/dist/services/videoIngestion.js +0 -104
  113. package/dist/services/videoIngestion.js.map +0 -1
  114. package/dist/tools/captions/captionGenerator.d.ts +0 -84
  115. package/dist/tools/captions/captionGenerator.d.ts.map +0 -1
  116. package/dist/tools/captions/captionGenerator.js +0 -390
  117. package/dist/tools/captions/captionGenerator.js.map +0 -1
  118. package/dist/tools/ffmpeg/aspectRatio.d.ts +0 -101
  119. package/dist/tools/ffmpeg/aspectRatio.d.ts.map +0 -1
  120. package/dist/tools/ffmpeg/aspectRatio.js +0 -339
  121. package/dist/tools/ffmpeg/aspectRatio.js.map +0 -1
  122. package/dist/tools/ffmpeg/audioExtraction.d.ts +0 -16
  123. package/dist/tools/ffmpeg/audioExtraction.d.ts.map +0 -1
  124. package/dist/tools/ffmpeg/audioExtraction.js +0 -87
  125. package/dist/tools/ffmpeg/audioExtraction.js.map +0 -1
  126. package/dist/tools/ffmpeg/captionBurning.d.ts +0 -8
  127. package/dist/tools/ffmpeg/captionBurning.d.ts.map +0 -1
  128. package/dist/tools/ffmpeg/captionBurning.js +0 -72
  129. package/dist/tools/ffmpeg/captionBurning.js.map +0 -1
  130. package/dist/tools/ffmpeg/clipExtraction.d.ts +0 -38
  131. package/dist/tools/ffmpeg/clipExtraction.d.ts.map +0 -1
  132. package/dist/tools/ffmpeg/clipExtraction.js +0 -215
  133. package/dist/tools/ffmpeg/clipExtraction.js.map +0 -1
  134. package/dist/tools/ffmpeg/faceDetection.d.ts +0 -127
  135. package/dist/tools/ffmpeg/faceDetection.d.ts.map +0 -1
  136. package/dist/tools/ffmpeg/faceDetection.js +0 -501
  137. package/dist/tools/ffmpeg/faceDetection.js.map +0 -1
  138. package/dist/tools/ffmpeg/frameCapture.d.ts +0 -10
  139. package/dist/tools/ffmpeg/frameCapture.d.ts.map +0 -1
  140. package/dist/tools/ffmpeg/frameCapture.js +0 -49
  141. package/dist/tools/ffmpeg/frameCapture.js.map +0 -1
  142. package/dist/tools/ffmpeg/silenceDetection.d.ts +0 -10
  143. package/dist/tools/ffmpeg/silenceDetection.d.ts.map +0 -1
  144. package/dist/tools/ffmpeg/silenceDetection.js +0 -56
  145. package/dist/tools/ffmpeg/silenceDetection.js.map +0 -1
  146. package/dist/tools/ffmpeg/singlePassEdit.d.ts +0 -25
  147. package/dist/tools/ffmpeg/singlePassEdit.d.ts.map +0 -1
  148. package/dist/tools/ffmpeg/singlePassEdit.js +0 -124
  149. package/dist/tools/ffmpeg/singlePassEdit.js.map +0 -1
  150. package/dist/tools/search/exaClient.d.ts +0 -8
  151. package/dist/tools/search/exaClient.d.ts.map +0 -1
  152. package/dist/tools/search/exaClient.js +0 -38
  153. package/dist/tools/search/exaClient.js.map +0 -1
  154. package/dist/tools/whisper/whisperClient.d.ts +0 -3
  155. package/dist/tools/whisper/whisperClient.d.ts.map +0 -1
  156. package/dist/tools/whisper/whisperClient.js +0 -77
  157. package/dist/tools/whisper/whisperClient.js.map +0 -1
  158. package/dist/types/index.d.ts +0 -305
  159. package/dist/types/index.d.ts.map +0 -1
  160. package/dist/types/index.js +0 -44
  161. package/dist/types/index.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts","../src/config/environment.ts","../src/services/fileWatcher.ts","../src/config/logger.ts","../src/pipeline.ts","../src/services/videoIngestion.ts","../src/config/ffmpegResolver.ts","../src/services/transcription.ts","../src/tools/ffmpeg/audioExtraction.ts","../src/tools/whisper/whisperClient.ts","../src/config/brand.ts","../src/services/captionGeneration.ts","../src/tools/captions/captionGenerator.ts","../src/agents/SummaryAgent.ts","../src/providers/CopilotProvider.ts","../src/providers/OpenAIProvider.ts","../src/config/pricing.ts","../src/providers/ClaudeProvider.ts","../src/providers/index.ts","../src/services/costTracker.ts","../src/agents/BaseAgent.ts","../src/tools/ffmpeg/frameCapture.ts","../src/tools/ffmpeg/clipExtraction.ts","../src/tools/ffmpeg/captionBurning.ts","../src/tools/ffmpeg/aspectRatio.ts","../src/tools/ffmpeg/faceDetection.ts","../src/agents/ShortsAgent.ts","../src/agents/MediumVideoAgent.ts","../src/agents/SocialMediaAgent.ts","../src/tools/search/exaClient.ts","../src/agents/BlogAgent.ts","../src/agents/ChapterAgent.ts","../src/services/gitOperations.ts","../src/agents/SilenceRemovalAgent.ts","../src/tools/ffmpeg/silenceDetection.ts","../src/tools/ffmpeg/singlePassEdit.ts","../src/commands/doctor.ts"],"sourcesContent":["import { Command } from 'commander'\r\nimport { initConfig, validateRequiredKeys, getConfig } from './config/environment'\r\nimport type { CLIOptions } from './config/environment'\r\nimport { FileWatcher } from './services/fileWatcher'\r\nimport { processVideoSafe } from './pipeline'\r\nimport logger, { setVerbose } from './config/logger'\r\nimport { runDoctor } from './commands/doctor'\r\nimport path from 'path'\r\nimport { readFileSync } from 'fs'\r\nimport { fileURLToPath } from 'url'\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\r\nconst pkg = JSON.parse(readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf-8'))\r\n\r\nconst BANNER = `\r\n╔══════════════════════════════════════╗\r\n║ VidPipe v${pkg.version.padEnd(24)}║\r\n╚══════════════════════════════════════╝\r\n`\r\n\r\nconst program = new Command()\r\n\r\nprogram\r\n .name('vidpipe')\r\n .description('AI-powered video content pipeline: transcribe, summarize, generate shorts, captions, and social posts')\r\n .version(pkg.version, '-V, --version')\r\n .argument('[video-path]', 'Path to a video file to process (implies --once)')\r\n .option('--watch-dir <path>', 'Folder to watch for new recordings (default: env WATCH_FOLDER)')\r\n .option('--output-dir <path>', 'Output directory for processed videos (default: ./recordings)')\r\n .option('--openai-key <key>', 'OpenAI API key (default: env OPENAI_API_KEY)')\r\n .option('--exa-key <key>', 'Exa AI API key for web search (default: env EXA_API_KEY)')\r\n .option('--once', 'Process a single video and exit (no watching)')\r\n .option('--brand <path>', 'Path to brand.json config (default: ./brand.json)')\r\n .option('--no-git', 'Skip git commit/push stage')\r\n .option('--no-silence-removal', 'Skip silence removal stage')\r\n .option('--no-shorts', 'Skip shorts generation')\r\n .option('--no-medium-clips', 'Skip medium clip generation')\r\n .option('--no-social', 'Skip social media post generation')\r\n .option('--no-captions', 'Skip caption generation/burning')\r\n .option('-v, --verbose', 'Verbose logging')\r\n .option('--doctor', 'Check all prerequisites and exit')\r\n\r\nprogram.parse()\r\n\r\nconst opts = program.opts()\r\n\r\n// Handle --doctor before anything else\r\nif (opts.doctor) {\r\n runDoctor()\r\n // runDoctor() calls process.exit(); this is a safety fallback\r\n process.exit(0)\r\n}\r\n\r\nconst videoArg = program.args[0]\r\nconst onceMode: boolean = opts.once || !!videoArg\r\n\r\nconst cliOptions: CLIOptions = {\r\n watchDir: opts.watchDir,\r\n outputDir: opts.outputDir,\r\n openaiKey: opts.openaiKey,\r\n exaKey: opts.exaKey,\r\n brand: opts.brand,\r\n verbose: opts.verbose,\r\n git: opts.git,\r\n silenceRemoval: opts.silenceRemoval,\r\n shorts: opts.shorts,\r\n mediumClips: opts.mediumClips,\r\n social: opts.social,\r\n captions: opts.captions,\r\n}\r\n\r\nconst queue: string[] = []\r\nlet processing = false\r\nlet shutdownRequested = false\r\nlet watcher: FileWatcher | null = null\r\n\r\nasync function processQueue(): Promise<void> {\r\n if (processing || queue.length === 0) return\r\n processing = true\r\n\r\n try {\r\n while (queue.length > 0) {\r\n const videoPath = queue.shift()!\r\n logger.info(`Processing video: ${videoPath}`)\r\n await processVideoSafe(videoPath)\r\n\r\n if (onceMode) {\r\n logger.info('--once flag set, exiting after first video.')\r\n await shutdown()\r\n return\r\n }\r\n\r\n if (shutdownRequested) break\r\n }\r\n } finally {\r\n processing = false\r\n }\r\n}\r\n\r\nfunction enqueue(videoPath: string): void {\r\n queue.push(videoPath)\r\n logger.info(`Queued video: ${videoPath} (queue length: ${queue.length})`)\r\n processQueue().catch(err => logger.error('Queue processing error:', err))\r\n}\r\n\r\nasync function shutdown(): Promise<void> {\r\n if (shutdownRequested) return\r\n shutdownRequested = true\r\n logger.info('Shutting down...')\r\n\r\n if (watcher) {\r\n watcher.stop()\r\n }\r\n\r\n while (processing) {\r\n await new Promise((resolve) => setTimeout(resolve, 500))\r\n }\r\n\r\n logger.info('Goodbye.')\r\n process.exit(0)\r\n}\r\n\r\nasync function main(): Promise<void> {\r\n logger.info(BANNER)\r\n\r\n initConfig(cliOptions)\r\n if (opts.verbose) setVerbose()\r\n validateRequiredKeys()\r\n\r\n const config = getConfig()\r\n\r\n logger.info(`Watch folder: ${config.WATCH_FOLDER}`)\r\n logger.info(`Output dir: ${config.OUTPUT_DIR}`)\r\n\r\n // Direct file mode: process a specific video and exit\r\n if (videoArg) {\r\n const resolvedPath = path.resolve(videoArg)\r\n logger.info(`Processing single video: ${resolvedPath}`)\r\n await processVideoSafe(resolvedPath)\r\n logger.info('Done.')\r\n process.exit(0)\r\n }\r\n\r\n // Watch mode\r\n watcher = new FileWatcher()\r\n watcher.on('new-video', (filePath: string) => {\r\n enqueue(filePath)\r\n })\r\n watcher.start()\r\n\r\n if (onceMode) {\r\n logger.info('Running in --once mode. Will exit after processing the next video.')\r\n } else {\r\n logger.info('Watching for new videos. Press Ctrl+C to stop.')\r\n }\r\n}\r\n\r\nprocess.on('SIGINT', () => shutdown())\r\nprocess.on('SIGTERM', () => shutdown())\r\n\r\nmain().catch((err) => {\r\n logger.error(`Fatal error: ${err instanceof Error ? err.message : String(err)}`)\r\n process.exit(1)\r\n})\r\n","import path from 'path'\r\nimport fs from 'fs'\r\nimport dotenv from 'dotenv'\r\n\r\n// Load .env file from repo root\r\nconst envPath = path.join(process.cwd(), '.env')\r\nif (fs.existsSync(envPath)) {\r\n dotenv.config({ path: envPath })\r\n}\r\n\r\nexport interface AppEnvironment {\r\n OPENAI_API_KEY: string\r\n WATCH_FOLDER: string\r\n REPO_ROOT: string\r\n FFMPEG_PATH: string\r\n FFPROBE_PATH: string\r\n EXA_API_KEY: string\r\n LLM_PROVIDER: string\r\n LLM_MODEL: string\r\n ANTHROPIC_API_KEY: string\r\n OUTPUT_DIR: string\r\n BRAND_PATH: string\r\n VERBOSE: boolean\r\n SKIP_GIT: boolean\r\n SKIP_SILENCE_REMOVAL: boolean\r\n SKIP_SHORTS: boolean\r\n SKIP_MEDIUM_CLIPS: boolean\r\n SKIP_SOCIAL: boolean\r\n SKIP_CAPTIONS: boolean\r\n}\r\n\r\nexport interface CLIOptions {\r\n watchDir?: string\r\n outputDir?: string\r\n openaiKey?: string\r\n exaKey?: string\r\n brand?: string\r\n verbose?: boolean\r\n git?: boolean\r\n silenceRemoval?: boolean\r\n shorts?: boolean\r\n mediumClips?: boolean\r\n social?: boolean\r\n captions?: boolean\r\n}\r\n\r\nlet config: AppEnvironment | null = null\r\n\r\nexport function validateRequiredKeys(): void {\r\n if (!config?.OPENAI_API_KEY && !process.env.OPENAI_API_KEY) {\r\n throw new Error('Missing required: OPENAI_API_KEY (set via --openai-key or env var)')\r\n }\r\n}\r\n\r\n/** Merge CLI options → env vars → defaults. Call before getConfig(). */\r\nexport function initConfig(cli: CLIOptions = {}): AppEnvironment {\r\n const repoRoot = process.env.REPO_ROOT || process.cwd()\r\n\r\n config = {\r\n OPENAI_API_KEY: cli.openaiKey || process.env.OPENAI_API_KEY || '',\r\n WATCH_FOLDER: cli.watchDir || process.env.WATCH_FOLDER || path.join(repoRoot, 'watch'),\r\n REPO_ROOT: repoRoot,\r\n FFMPEG_PATH: process.env.FFMPEG_PATH || 'ffmpeg', // legacy; prefer ffmpegResolver\r\n FFPROBE_PATH: process.env.FFPROBE_PATH || 'ffprobe', // legacy; prefer ffmpegResolver\r\n EXA_API_KEY: cli.exaKey || process.env.EXA_API_KEY || '',\r\n LLM_PROVIDER: process.env.LLM_PROVIDER || 'copilot',\r\n LLM_MODEL: process.env.LLM_MODEL || '',\r\n ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',\r\n OUTPUT_DIR:cli.outputDir || process.env.OUTPUT_DIR || path.join(repoRoot, 'recordings'),\r\n BRAND_PATH: cli.brand || process.env.BRAND_PATH || path.join(repoRoot, 'brand.json'),\r\n VERBOSE: cli.verbose ?? false,\r\n SKIP_GIT: cli.git === false,\r\n SKIP_SILENCE_REMOVAL: cli.silenceRemoval === false,\r\n SKIP_SHORTS: cli.shorts === false,\r\n SKIP_MEDIUM_CLIPS: cli.mediumClips === false,\r\n SKIP_SOCIAL: cli.social === false,\r\n SKIP_CAPTIONS: cli.captions === false,\r\n }\r\n\r\n return config\r\n}\r\n\r\nexport function getConfig(): AppEnvironment {\r\n if (config) {\r\n return config\r\n }\r\n\r\n // Fallback: init with no CLI options (pure env-var mode)\r\n return initConfig()\r\n}\r\n","import { watch, FSWatcher } from 'chokidar'\r\nimport { getConfig } from '../config/environment'\r\nimport { EventEmitter } from 'events'\r\nimport path from 'path'\r\nimport fs from 'fs'\r\nimport logger from '../config/logger'\r\n\r\nexport interface FileWatcherOptions {\r\n processExisting?: boolean\r\n}\r\n\r\nexport class FileWatcher extends EventEmitter {\r\n private watchFolder: string\r\n private watcher: FSWatcher | null = null\r\n private processExisting: boolean\r\n\r\n constructor(options: FileWatcherOptions = {}) {\r\n super()\r\n const config = getConfig()\r\n this.watchFolder = config.WATCH_FOLDER\r\n this.processExisting = options.processExisting ?? false\r\n\r\n if (!fs.existsSync(this.watchFolder)) {\r\n fs.mkdirSync(this.watchFolder, { recursive: true })\r\n logger.info(`Created watch folder: ${this.watchFolder}`)\r\n }\r\n }\r\n\r\n private static readonly MIN_FILE_SIZE = 1024 * 1024 // 1MB\r\n private static readonly EXTRA_STABILITY_DELAY = 3000\r\n\r\n /** Read file size, wait, read again — if it changed the file is still being written. */\r\n private async isFileStable(filePath: string): Promise<boolean> {\r\n try {\r\n const sizeBefore = fs.statSync(filePath).size\r\n await new Promise((resolve) => setTimeout(resolve, FileWatcher.EXTRA_STABILITY_DELAY))\r\n const sizeAfter = fs.statSync(filePath).size\r\n return sizeBefore === sizeAfter\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n private async handleDetectedFile(filePath: string): Promise<void> {\r\n if (path.extname(filePath).toLowerCase() !== '.mp4') {\r\n logger.debug(`[watcher] Ignoring non-mp4 file: ${filePath}`)\r\n return\r\n }\r\n\r\n let fileSize: number\r\n try {\r\n fileSize = fs.statSync(filePath).size\r\n } catch (err) {\r\n logger.warn(`[watcher] Could not stat file (may have been removed): ${filePath}`)\r\n return\r\n }\r\n\r\n logger.debug(`[watcher] File size: ${(fileSize / 1024 / 1024).toFixed(1)} MB — ${filePath}`)\r\n if (fileSize < FileWatcher.MIN_FILE_SIZE) {\r\n logger.warn(`Skipping small file (${fileSize} bytes), likely a failed recording: ${filePath}`)\r\n return\r\n }\r\n\r\n const stable = await this.isFileStable(filePath)\r\n if (!stable) {\r\n logger.warn(`File is still being written, skipping for now: ${filePath}`)\r\n return\r\n }\r\n\r\n logger.info(`New video detected: ${filePath}`)\r\n this.emit('new-video', filePath)\r\n }\r\n\r\n private scanExistingFiles(): void {\r\n const files = fs.readdirSync(this.watchFolder)\r\n for (const file of files) {\r\n if (path.extname(file).toLowerCase() === '.mp4') {\r\n const filePath = path.join(this.watchFolder, file)\r\n this.handleDetectedFile(filePath).catch(err =>\r\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\r\n )\r\n }\r\n }\r\n }\r\n\r\n start(): void {\r\n this.watcher = watch(this.watchFolder, {\r\n persistent: true,\r\n ignoreInitial: true,\r\n depth: 0,\r\n atomic: 100,\r\n // Polling is more reliable on Windows for detecting renames (e.g. Bandicam temp→final)\r\n usePolling: true,\r\n interval: 500,\r\n awaitWriteFinish: {\r\n stabilityThreshold: 3000,\r\n pollInterval: 200,\r\n },\r\n })\r\n\r\n this.watcher.on('add', (filePath: string) => {\r\n logger.debug(`[watcher] 'add' event: ${filePath}`)\r\n this.handleDetectedFile(filePath).catch(err =>\r\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\r\n )\r\n })\r\n\r\n this.watcher.on('change', (filePath: string) => {\r\n logger.debug(`[watcher] 'change' event: ${filePath}`)\r\n if (path.extname(filePath).toLowerCase() !== '.mp4') return\r\n logger.info(`Change detected on video file: ${filePath}`)\r\n this.handleDetectedFile(filePath).catch(err =>\r\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\r\n )\r\n })\r\n\r\n this.watcher.on('unlink', (filePath: string) => {\r\n logger.debug(`[watcher] 'unlink' event: ${filePath}`)\r\n })\r\n\r\n this.watcher.on('raw', (event: string, rawPath: string, details: unknown) => {\r\n logger.debug(`[watcher] raw event=${event} path=${rawPath}`)\r\n })\r\n\r\n this.watcher.on('error', (error: unknown) => {\r\n logger.error(`File watcher error: ${error instanceof Error ? error.message : String(error)}`)\r\n })\r\n\r\n this.watcher.on('ready', () => {\r\n logger.info('File watcher is fully initialized and ready')\r\n if (this.processExisting) {\r\n this.scanExistingFiles()\r\n }\r\n })\r\n\r\n logger.info(`Watching for new .mp4 files in: ${this.watchFolder}`)\r\n }\r\n\r\n stop(): void {\r\n if (this.watcher) {\r\n this.watcher.close()\r\n this.watcher = null\r\n logger.info('File watcher stopped')\r\n }\r\n }\r\n}\r\n","import winston from 'winston'\r\n\r\nconst logger = winston.createLogger({\r\n level: 'info',\r\n format: winston.format.combine(\r\n winston.format.timestamp(),\r\n winston.format.printf(({ timestamp, level, message }) => {\r\n return `${timestamp} [${level.toUpperCase()}]: ${message}`\r\n })\r\n ),\r\n transports: [new winston.transports.Console()],\r\n})\r\n\r\nexport function setVerbose(): void {\r\n logger.level = 'debug'\r\n}\r\n\r\nexport default logger\r\n","import path from 'path'\r\nimport { promises as fs } from 'fs'\r\nimport logger from './config/logger'\r\nimport { getConfig } from './config/environment'\r\nimport { ingestVideo } from './services/videoIngestion'\r\nimport { transcribeVideo } from './services/transcription'\r\nimport { generateCaptions } from './services/captionGeneration'\r\nimport { generateSummary } from './agents/SummaryAgent'\r\nimport { generateShorts } from './agents/ShortsAgent'\r\nimport { generateMediumClips } from './agents/MediumVideoAgent'\r\nimport { generateSocialPosts, generateShortPosts } from './agents/SocialMediaAgent'\r\nimport { generateBlogPost } from './agents/BlogAgent'\r\nimport { generateChapters } from './agents/ChapterAgent'\r\nimport { commitAndPush } from './services/gitOperations'\r\nimport { removeDeadSilence } from './agents/SilenceRemovalAgent'\r\nimport { burnCaptions } from './tools/ffmpeg/captionBurning'\r\nimport { singlePassEditAndCaption } from './tools/ffmpeg/singlePassEdit'\r\nimport { costTracker } from './services/costTracker.js'\r\nimport type { CostReport } from './services/costTracker.js'\r\nimport type {\r\n VideoFile,\r\n Transcript,\r\n VideoSummary,\r\n ShortClip,\r\n MediumClip,\r\n SocialPost,\r\n StageResult,\r\n PipelineResult,\r\n PipelineStage,\r\n SilenceRemovalResult,\r\n Chapter,\r\n} from './types'\r\nimport { PipelineStage as Stage } from './types'\r\n\r\n/**\r\n * Execute a single pipeline stage with error isolation and timing.\r\n *\r\n * ### Stage contract\r\n * - Each stage is wrapped in a try/catch so a failure **does not abort** the\r\n * pipeline. Subsequent stages proceed with whatever data is available.\r\n * - Returns `undefined` on failure (callers must null-check before using the result).\r\n * - Records success/failure, error message, and wall-clock duration in `stageResults`\r\n * for the pipeline summary.\r\n *\r\n * This design lets the pipeline produce partial results — e.g. if shorts\r\n * generation fails, the summary and social posts can still be generated\r\n * from the transcript.\r\n *\r\n * @param stageName - Enum value identifying the stage (used in logs and results)\r\n * @param fn - Async function that performs the stage's work\r\n * @param stageResults - Mutable array that accumulates per-stage outcome records\r\n * @returns The stage result on success, or `undefined` on failure\r\n */\r\nexport async function runStage<T>(\r\n stageName: PipelineStage,\r\n fn: () => Promise<T>,\r\n stageResults: StageResult[],\r\n): Promise<T | undefined> {\r\n costTracker.setStage(stageName)\r\n const start = Date.now()\r\n try {\r\n const result = await fn()\r\n const duration = Date.now() - start\r\n stageResults.push({ stage: stageName, success: true, duration })\r\n logger.info(`Stage ${stageName} completed in ${duration}ms`)\r\n return result\r\n } catch (err: unknown) {\r\n const duration = Date.now() - start\r\n const message = err instanceof Error ? err.message : String(err)\r\n stageResults.push({ stage: stageName, success: false, error: message, duration })\r\n logger.error(`Stage ${stageName} failed after ${duration}ms: ${message}`)\r\n return undefined\r\n }\r\n}\r\n\r\n/**\r\n * Adjust transcript timestamps to account for removed silence segments.\r\n * Shifts all timestamps by subtracting the cumulative removed duration before each point.\r\n */\r\nexport function adjustTranscript(\r\n transcript: Transcript,\r\n removals: { start: number; end: number }[],\r\n): Transcript {\r\n const sorted = [...removals].sort((a, b) => a.start - b.start)\r\n\r\n function adjustTime(t: number): number {\r\n let offset = 0\r\n for (const r of sorted) {\r\n if (t <= r.start) break\r\n if (t >= r.end) {\r\n offset += r.end - r.start\r\n } else {\r\n // timestamp is inside a removed region — snap to removal start\r\n offset += t - r.start\r\n }\r\n }\r\n return t - offset\r\n }\r\n\r\n return {\r\n ...transcript,\r\n duration: adjustTime(transcript.duration),\r\n segments: transcript.segments\r\n .filter(seg => !sorted.some(r => seg.start >= r.start && seg.end <= r.end))\r\n .map(seg => ({\r\n ...seg,\r\n start: adjustTime(seg.start),\r\n end: adjustTime(seg.end),\r\n })),\r\n words: transcript.words\r\n .filter(w => !sorted.some(r => w.start >= r.start && w.end <= r.end))\r\n .map(w => ({\r\n ...w,\r\n start: adjustTime(w.start),\r\n end: adjustTime(w.end),\r\n })),\r\n }\r\n}\r\n\r\n/**\r\n * Run the full video processing pipeline.\r\n *\r\n * ### Stage ordering and data flow\r\n * 1. **Ingest** — extracts metadata (slug, duration, paths). Required; aborts if failed.\r\n * 2. **Transcribe** — Whisper transcription with word-level timestamps.\r\n * 3. **Silence removal** — trims dead air from the video and adjusts the transcript\r\n * timestamps accordingly. Produces an `adjustedTranscript` for captions.\r\n * 4. **Captions** — generates SRT/VTT/ASS files from the (adjusted) transcript.\r\n * 5. **Caption burn** — renders captions into the video using FFmpeg. Prefers a\r\n * single-pass approach (silence removal + captions in one encode) when possible.\r\n * 6. **Shorts** — AI-selected short clips. Uses the **original** transcript because\r\n * clips are cut from the original (unedited) video.\r\n * 7. **Medium clips** — longer AI-selected clips (same original-transcript reasoning).\r\n * 8. **Chapters** — topic-boundary detection for YouTube chapters.\r\n * 9. **Summary** — README generation (runs after shorts/chapters so it can reference them).\r\n * 10–12. **Social posts** — platform-specific posts for the full video and each clip.\r\n * 13. **Blog** — long-form blog post from transcript + summary.\r\n * 14. **Git push** — commits all generated assets and pushes.\r\n *\r\n * ### Why failures don't abort\r\n * Each stage runs through {@link runStage} which catches errors. This means a\r\n * transcription failure still lets git-push run (committing whatever was produced),\r\n * and a shorts failure doesn't block summary generation.\r\n */\r\nexport async function processVideo(videoPath: string): Promise<PipelineResult> {\r\n const pipelineStart = Date.now()\r\n const stageResults: StageResult[] = []\r\n const cfg = getConfig()\r\n\r\n costTracker.reset()\r\n logger.info(`Pipeline starting for: ${videoPath}`)\r\n\r\n // 1. Ingestion — required for all subsequent stages\r\n const video = await runStage<VideoFile>(Stage.Ingestion, () => ingestVideo(videoPath), stageResults)\r\n if (!video) {\r\n const totalDuration = Date.now() - pipelineStart\r\n logger.error('Ingestion failed — cannot proceed without video metadata')\r\n 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 }\r\n }\r\n\r\n // 2. Transcription\r\n let transcript: Transcript | undefined\r\n transcript = await runStage<Transcript>(Stage.Transcription, () => transcribeVideo(video), stageResults)\r\n\r\n // 3. Silence Removal (context-aware)\r\n let editedVideoPath: string | undefined\r\n let adjustedTranscript: Transcript | undefined\r\n let silenceRemovals: { start: number; end: number }[] = []\r\n let silenceKeepSegments: { start: number; end: number }[] | undefined\r\n\r\n if (transcript && !cfg.SKIP_SILENCE_REMOVAL) {\r\n const result = await runStage<SilenceRemovalResult>(Stage.SilenceRemoval, () => removeDeadSilence(video, transcript!), stageResults)\r\n if (result && result.wasEdited) {\r\n editedVideoPath = result.editedPath\r\n silenceRemovals = result.removals\r\n silenceKeepSegments = result.keepSegments\r\n adjustedTranscript = adjustTranscript(transcript, silenceRemovals)\r\n\r\n // Validate: check that adjusted transcript duration is close to edited video duration\r\n const totalRemoved = silenceRemovals.reduce((sum, r) => sum + (r.end - r.start), 0)\r\n const expectedDuration = transcript.duration - totalRemoved\r\n const adjustedDuration = adjustedTranscript.duration\r\n const drift = Math.abs(expectedDuration - adjustedDuration)\r\n 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`)\r\n\r\n await fs.writeFile(\r\n path.join(video.videoDir, 'transcript-edited.json'),\r\n JSON.stringify(adjustedTranscript, null, 2),\r\n )\r\n }\r\n }\r\n\r\n // Use adjusted transcript for captions (if silence was removed), original otherwise\r\n const captionTranscript = adjustedTranscript ?? transcript\r\n\r\n // 4. Captions (fast, no AI needed) — generate from the right transcript\r\n let captions: string[] | undefined\r\n if (captionTranscript && !cfg.SKIP_CAPTIONS) {\r\n captions = await runStage<string[]>(Stage.Captions, () => generateCaptions(video, captionTranscript), stageResults)\r\n }\r\n\r\n // 5. Caption Burn — use single-pass (silence removal + captions) when possible\r\n let captionedVideoPath: string | undefined\r\n if (captions && !cfg.SKIP_CAPTIONS) {\r\n const assFile = captions.find((p) => p.endsWith('.ass'))\r\n if (assFile && silenceKeepSegments) {\r\n // Single-pass: re-do silence removal + burn captions from ORIGINAL video in one encode\r\n // This guarantees frame-accurate cuts with perfectly aligned captions\r\n const captionedOutput = path.join(video.videoDir, `${video.slug}-captioned.mp4`)\r\n captionedVideoPath = await runStage<string>(\r\n Stage.CaptionBurn,\r\n () => singlePassEditAndCaption(video.repoPath, silenceKeepSegments!, assFile, captionedOutput),\r\n stageResults,\r\n )\r\n } else if (assFile) {\r\n // No silence removal — just burn captions into original video\r\n const videoToBurn = editedVideoPath ?? video.repoPath\r\n const captionedOutput = path.join(video.videoDir, `${video.slug}-captioned.mp4`)\r\n captionedVideoPath = await runStage<string>(\r\n Stage.CaptionBurn,\r\n () => burnCaptions(videoToBurn, assFile, captionedOutput),\r\n stageResults,\r\n )\r\n }\r\n }\r\n\r\n // 6. Shorts — use ORIGINAL transcript (shorts reference original video timestamps)\r\n let shorts: ShortClip[] = []\r\n if (transcript && !cfg.SKIP_SHORTS) {\r\n const result = await runStage<ShortClip[]>(Stage.Shorts, () => generateShorts(video, transcript), stageResults)\r\n if (result) shorts = result\r\n }\r\n\r\n // 7. Medium Clips — use ORIGINAL transcript (medium clips reference original video timestamps)\r\n let mediumClips: MediumClip[] = []\r\n if (transcript && !cfg.SKIP_MEDIUM_CLIPS) {\r\n const result = await runStage<MediumClip[]>(Stage.MediumClips, () => generateMediumClips(video, transcript), stageResults)\r\n if (result) mediumClips = result\r\n }\r\n\r\n // 8. Chapters — analyse transcript for topic boundaries\r\n let chapters: Chapter[] | undefined\r\n if (transcript) {\r\n chapters = await runStage<Chapter[]>(Stage.Chapters, () => generateChapters(video, transcript), stageResults)\r\n }\r\n\r\n // 9. Summary (after shorts, medium clips, and chapters so the README can reference them)\r\n let summary: VideoSummary | undefined\r\n if (transcript) {\r\n summary = await runStage<VideoSummary>(Stage.Summary, () => generateSummary(video, transcript, shorts, chapters), stageResults)\r\n }\r\n\r\n // 10. Social Media\r\n let socialPosts: SocialPost[] = []\r\n if (transcript && summary && !cfg.SKIP_SOCIAL) {\r\n const result = await runStage<SocialPost[]>(\r\n Stage.SocialMedia,\r\n () => generateSocialPosts(video, transcript, summary, path.join(video.videoDir, 'social-posts')),\r\n stageResults,\r\n )\r\n if (result) socialPosts = result\r\n }\r\n\r\n // 11. Short Posts — generate social posts per short clip\r\n if (transcript && shorts.length > 0 && !cfg.SKIP_SOCIAL) {\r\n await runStage<void>(\r\n Stage.ShortPosts,\r\n async () => {\r\n for (const short of shorts) {\r\n const posts = await generateShortPosts(video, short, transcript)\r\n socialPosts.push(...posts)\r\n }\r\n },\r\n stageResults,\r\n )\r\n }\r\n\r\n // 12. Medium Clip Posts — generate social posts per medium clip\r\n if (transcript && mediumClips.length > 0 && !cfg.SKIP_SOCIAL) {\r\n await runStage<void>(\r\n Stage.MediumClipPosts,\r\n async () => {\r\n for (const clip of mediumClips) {\r\n const asShortClip: ShortClip = {\r\n id: clip.id,\r\n title: clip.title,\r\n slug: clip.slug,\r\n segments: clip.segments,\r\n totalDuration: clip.totalDuration,\r\n outputPath: clip.outputPath,\r\n captionedPath: clip.captionedPath,\r\n description: clip.description,\r\n tags: clip.tags,\r\n }\r\n const posts = await generateShortPosts(video, asShortClip, transcript)\r\n // Move posts to medium-clips/{slug}/posts/\r\n const clipsDir = path.join(path.dirname(video.repoPath), 'medium-clips')\r\n const postsDir = path.join(clipsDir, clip.slug, 'posts')\r\n await fs.mkdir(postsDir, { recursive: true })\r\n for (const post of posts) {\r\n const destPath = path.join(postsDir, path.basename(post.outputPath))\r\n await fs.copyFile(post.outputPath, destPath)\r\n await fs.unlink(post.outputPath).catch(() => {})\r\n post.outputPath = destPath\r\n }\r\n socialPosts.push(...posts)\r\n }\r\n },\r\n stageResults,\r\n )\r\n }\r\n\r\n // 13. Blog Post\r\n let blogPost: string | undefined\r\n if (transcript && summary) {\r\n blogPost = await runStage<string>(\r\n Stage.Blog,\r\n () => generateBlogPost(video, transcript, summary),\r\n stageResults,\r\n )\r\n }\r\n\r\n // 14. Git\r\n if (!cfg.SKIP_GIT) {\r\n await runStage<void>(Stage.GitPush, () => commitAndPush(video.slug), stageResults)\r\n }\r\n\r\n const totalDuration = Date.now() - pipelineStart\r\n\r\n // Cost tracking report\r\n const report = costTracker.getReport()\r\n if (report.records.length > 0) {\r\n logger.info(costTracker.formatReport())\r\n const costMd = generateCostMarkdown(report)\r\n const costPath = path.join(video.videoDir, 'cost-report.md')\r\n await fs.writeFile(costPath, costMd, 'utf-8')\r\n logger.info(`Cost report saved: ${costPath}`)\r\n }\r\n\r\n logger.info(`Pipeline completed in ${totalDuration}ms`)\r\n\r\n return {\r\n video,\r\n transcript,\r\n editedVideoPath,\r\n captions,\r\n captionedVideoPath,\r\n summary,\r\n chapters,\r\n shorts,\r\n mediumClips,\r\n socialPosts,\r\n blogPost,\r\n stageResults,\r\n totalDuration,\r\n }\r\n}\r\n\r\nfunction generateCostMarkdown(report: CostReport): string {\r\n let md = '# Pipeline Cost Report\\n\\n'\r\n md += `| Metric | Value |\\n|--------|-------|\\n`\r\n md += `| Total Cost | $${report.totalCostUSD.toFixed(4)} USD |\\n`\r\n if (report.totalPRUs > 0) md += `| Total PRUs | ${report.totalPRUs} |\\n`\r\n md += `| Input Tokens | ${report.totalTokens.input.toLocaleString()} |\\n`\r\n md += `| Output Tokens | ${report.totalTokens.output.toLocaleString()} |\\n`\r\n md += `| LLM Calls | ${report.records.length} |\\n\\n`\r\n\r\n if (Object.keys(report.byAgent).length > 0) {\r\n md += '## By Agent\\n\\n| Agent | Cost | PRUs | Calls |\\n|-------|------|------|-------|\\n'\r\n for (const [agent, data] of Object.entries(report.byAgent)) {\r\n md += `| ${agent} | $${data.costUSD.toFixed(4)} | ${data.prus} | ${data.calls} |\\n`\r\n }\r\n md += '\\n'\r\n }\r\n\r\n if (Object.keys(report.byModel).length > 1) {\r\n md += '## By Model\\n\\n| Model | Cost | PRUs | Calls |\\n|-------|------|------|-------|\\n'\r\n for (const [model, data] of Object.entries(report.byModel)) {\r\n md += `| ${model} | $${data.costUSD.toFixed(4)} | ${data.prus} | ${data.calls} |\\n`\r\n }\r\n md += '\\n'\r\n }\r\n\r\n return md\r\n}\r\n\r\nexport async function processVideoSafe(videoPath: string): Promise<PipelineResult | null> {\r\n try {\r\n return await processVideo(videoPath)\r\n } catch (err: unknown) {\r\n const message = err instanceof Error ? err.message : String(err)\r\n logger.error(`Pipeline failed with uncaught error: ${message}`)\r\n return null\r\n }\r\n}\r\n","import path from 'path'\r\nimport fs from 'fs'\r\nimport fsp from 'fs/promises'\r\nimport slugify from 'slugify'\r\nimport ffmpeg from 'fluent-ffmpeg'\r\nimport { VideoFile } from '../types'\r\nimport { getConfig } from '../config/environment'\r\nimport logger from '../config/logger'\r\nimport { getFFmpegPath, getFFprobePath } from '../config/ffmpegResolver.js'\r\n\r\nconst ffmpegBin = getFFmpegPath()\r\nconst ffprobeBin = getFFprobePath()\r\nffmpeg.setFfmpegPath(ffmpegBin)\r\nffmpeg.setFfprobePath(ffprobeBin)\r\n\r\nfunction getVideoMetadata(filePath: string): Promise<{ duration: number }> {\r\n return new Promise((resolve, reject) => {\r\n ffmpeg.ffprobe(filePath, (err, metadata) => {\r\n if (err) {\r\n return reject(err)\r\n }\r\n resolve({ duration: metadata.format.duration ?? 0 })\r\n })\r\n })\r\n}\r\n\r\nexport async function ingestVideo(sourcePath: string): Promise<VideoFile> {\r\n const config = getConfig()\r\n const baseName = path.basename(sourcePath, path.extname(sourcePath))\r\n const slug = slugify(baseName, { lower: true })\r\n\r\n const recordingsDir = path.join(config.OUTPUT_DIR, slug)\r\n const thumbnailsDir = path.join(recordingsDir, 'thumbnails')\r\n const shortsDir = path.join(recordingsDir, 'shorts')\r\n const socialPostsDir = path.join(recordingsDir, 'social-posts')\r\n\r\n logger.info(`Ingesting video: ${sourcePath} → ${slug}`)\r\n\r\n // Clean stale artifacts if output folder already exists\r\n if (fs.existsSync(recordingsDir)) {\r\n logger.warn(`Output folder already exists, cleaning previous artifacts: ${recordingsDir}`)\r\n\r\n const subDirs = ['thumbnails', 'shorts', 'social-posts', 'chapters', 'medium-clips', 'captions']\r\n for (const sub of subDirs) {\r\n await fsp.rm(path.join(recordingsDir, sub), { recursive: true, force: true })\r\n }\r\n\r\n const stalePatterns = [\r\n 'transcript.json', 'transcript-edited.json',\r\n 'captions.srt', 'captions.vtt', 'captions.ass',\r\n 'summary.md', 'blog-post.md', 'README.md',\r\n ]\r\n for (const pattern of stalePatterns) {\r\n await fsp.rm(path.join(recordingsDir, pattern), { force: true })\r\n }\r\n\r\n const files = await fsp.readdir(recordingsDir)\r\n for (const file of files) {\r\n if (file.endsWith('-edited.mp4') || file.endsWith('-captioned.mp4')) {\r\n await fsp.rm(path.join(recordingsDir, file), { force: true })\r\n }\r\n }\r\n }\r\n\r\n await fsp.mkdir(recordingsDir, { recursive: true })\r\n await fsp.mkdir(thumbnailsDir, { recursive: true })\r\n await fsp.mkdir(shortsDir, { recursive: true })\r\n await fsp.mkdir(socialPostsDir, { recursive: true })\r\n\r\n const destFilename = `${slug}.mp4`\r\n const destPath = path.join(recordingsDir, destFilename)\r\n\r\n let needsCopy = true\r\n try {\r\n const destStats = await fsp.stat(destPath)\r\n const srcStats = await fsp.stat(sourcePath)\r\n if (destStats.size === srcStats.size) {\r\n logger.info(`Video already copied (same size), skipping copy`)\r\n needsCopy = false\r\n }\r\n } catch {\r\n // Dest doesn't exist, need to copy\r\n }\r\n\r\n if (needsCopy) {\r\n await new Promise<void>((resolve, reject) => {\r\n const readStream = fs.createReadStream(sourcePath)\r\n const writeStream = fs.createWriteStream(destPath)\r\n readStream.on('error', reject)\r\n writeStream.on('error', reject)\r\n writeStream.on('finish', resolve)\r\n readStream.pipe(writeStream)\r\n })\r\n logger.info(`Copied video to ${destPath}`)\r\n }\r\n\r\n let duration = 0\r\n try {\r\n const meta = await getVideoMetadata(destPath)\r\n duration = meta.duration\r\n } catch (err) {\r\n logger.warn(`ffprobe failed, continuing without duration metadata: ${err instanceof Error ? err.message : String(err)}`)\r\n }\r\n const stats = await fsp.stat(destPath)\r\n\r\n logger.info(`Video metadata: duration=${duration}s, size=${stats.size} bytes`)\r\n\r\n return {\r\n originalPath: sourcePath,\r\n repoPath: destPath,\r\n videoDir: recordingsDir,\r\n slug,\r\n filename: destFilename,\r\n duration,\r\n size: stats.size,\r\n createdAt: new Date(),\r\n }\r\n}\r\n","import { createRequire } from 'module';\r\nimport { existsSync } from 'fs';\r\nimport logger from './logger.js';\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\nexport function getFFmpegPath(): string {\r\n if (process.env.FFMPEG_PATH) {\r\n logger.debug(`FFmpeg: using FFMPEG_PATH env var: ${process.env.FFMPEG_PATH}`);\r\n return process.env.FFMPEG_PATH;\r\n }\r\n try {\r\n const staticPath = require('ffmpeg-static') as string;\r\n if (staticPath && existsSync(staticPath)) {\r\n logger.debug(`FFmpeg: using ffmpeg-static: ${staticPath}`);\r\n return staticPath;\r\n }\r\n } catch { /* ffmpeg-static not available */ }\r\n logger.debug('FFmpeg: falling back to system PATH');\r\n return 'ffmpeg';\r\n}\r\n\r\nexport function getFFprobePath(): string {\r\n if (process.env.FFPROBE_PATH) {\r\n logger.debug(`FFprobe: using FFPROBE_PATH env var: ${process.env.FFPROBE_PATH}`);\r\n return process.env.FFPROBE_PATH;\r\n }\r\n try {\r\n const { path: probePath } = require('@ffprobe-installer/ffprobe') as { path: string };\r\n if (probePath && existsSync(probePath)) {\r\n logger.debug(`FFprobe: using @ffprobe-installer/ffprobe: ${probePath}`);\r\n return probePath;\r\n }\r\n } catch { /* @ffprobe-installer/ffprobe not available */ }\r\n logger.debug('FFprobe: falling back to system PATH');\r\n return 'ffprobe';\r\n}\r\n","import path from 'path'\r\nimport fsp from 'fs/promises'\r\nimport { extractAudio, splitAudioIntoChunks } from '../tools/ffmpeg/audioExtraction'\r\nimport { transcribeAudio } from '../tools/whisper/whisperClient'\r\nimport { VideoFile, Transcript, Segment, Word } from '../types'\r\nimport { getConfig } from '../config/environment'\r\nimport logger from '../config/logger'\r\n\r\nconst MAX_WHISPER_SIZE_MB = 25\r\n\r\nexport async function transcribeVideo(video: VideoFile): Promise<Transcript> {\r\n const config = getConfig()\r\n\r\n // 1. Create cache directory for temp audio files\r\n const cacheDir = path.join(config.REPO_ROOT, 'cache')\r\n await fsp.mkdir(cacheDir, { recursive: true })\r\n logger.info(`Cache directory ready: ${cacheDir}`)\r\n\r\n // 2. Extract audio as compressed MP3 (much smaller than WAV)\r\n const mp3Path = path.join(cacheDir, `${video.slug}.mp3`)\r\n logger.info(`Extracting audio for \"${video.slug}\"`)\r\n await extractAudio(video.repoPath, mp3Path)\r\n\r\n // 3. Check file size and chunk if necessary\r\n const stats = await fsp.stat(mp3Path)\r\n const fileSizeMB = stats.size / (1024 * 1024)\r\n logger.info(`Extracted audio: ${fileSizeMB.toFixed(1)}MB`)\r\n\r\n let transcript: Transcript\r\n\r\n if (fileSizeMB <= MAX_WHISPER_SIZE_MB) {\r\n // Single-file transcription\r\n logger.info(`Transcribing audio for \"${video.slug}\"`)\r\n transcript = await transcribeAudio(mp3Path)\r\n } else {\r\n // Chunk and transcribe (very long videos, 50+ min)\r\n logger.info(`Audio exceeds ${MAX_WHISPER_SIZE_MB}MB, splitting into chunks`)\r\n const chunkPaths = await splitAudioIntoChunks(mp3Path)\r\n transcript = await transcribeChunks(chunkPaths)\r\n\r\n // Clean up chunk files\r\n for (const chunkPath of chunkPaths) {\r\n if (chunkPath !== mp3Path) {\r\n await fsp.unlink(chunkPath).catch(() => {})\r\n }\r\n }\r\n }\r\n\r\n // 4. Save transcript JSON\r\n const transcriptDir = path.join(config.OUTPUT_DIR, video.slug)\r\n await fsp.mkdir(transcriptDir, { recursive: true })\r\n const transcriptPath = path.join(transcriptDir, 'transcript.json')\r\n await fsp.writeFile(transcriptPath, JSON.stringify(transcript, null, 2), 'utf-8')\r\n logger.info(`Transcript saved: ${transcriptPath}`)\r\n\r\n // 5. Clean up temp audio file\r\n await fsp.unlink(mp3Path).catch(() => {})\r\n logger.info(`Cleaned up temp file: ${mp3Path}`)\r\n\r\n // 6. Return the transcript\r\n logger.info(\r\n `Transcription complete for \"${video.slug}\" — ` +\r\n `${transcript.segments.length} segments, ${transcript.words.length} words`\r\n )\r\n return transcript\r\n}\r\n\r\n/**\r\n * Transcribe multiple audio chunks and merge results with adjusted timestamps.\r\n */\r\nasync function transcribeChunks(chunkPaths: string[]): Promise<Transcript> {\r\n let allText = ''\r\n const allSegments: Segment[] = []\r\n const allWords: Word[] = []\r\n let cumulativeOffset = 0\r\n let totalDuration = 0\r\n let language = 'unknown'\r\n\r\n for (let i = 0; i < chunkPaths.length; i++) {\r\n logger.info(`Transcribing chunk ${i + 1}/${chunkPaths.length}: ${chunkPaths[i]}`)\r\n const result = await transcribeAudio(chunkPaths[i])\r\n\r\n if (i === 0) language = result.language\r\n\r\n // Adjust timestamps by cumulative offset\r\n const offsetSegments = result.segments.map((s) => ({\r\n ...s,\r\n id: allSegments.length + s.id,\r\n start: s.start + cumulativeOffset,\r\n end: s.end + cumulativeOffset,\r\n words: s.words.map((w) => ({\r\n ...w,\r\n start: w.start + cumulativeOffset,\r\n end: w.end + cumulativeOffset,\r\n })),\r\n }))\r\n\r\n const offsetWords = result.words.map((w) => ({\r\n ...w,\r\n start: w.start + cumulativeOffset,\r\n end: w.end + cumulativeOffset,\r\n }))\r\n\r\n allText += (allText ? ' ' : '') + result.text\r\n allSegments.push(...offsetSegments)\r\n allWords.push(...offsetWords)\r\n\r\n cumulativeOffset += result.duration\r\n totalDuration += result.duration\r\n }\r\n\r\n return {\r\n text: allText,\r\n segments: allSegments,\r\n words: allWords,\r\n language,\r\n duration: totalDuration,\r\n }\r\n}\r\n","import ffmpeg from 'fluent-ffmpeg';\r\nimport { promises as fs } from 'fs';\r\nimport path from 'path';\r\nimport logger from '../../config/logger';\r\nimport { getFFmpegPath, getFFprobePath } from '../../config/ffmpegResolver.js';\r\n\r\nconst ffmpegPath = getFFmpegPath();\r\nconst ffprobePath = getFFprobePath();\r\nffmpeg.setFfmpegPath(ffmpegPath);\r\nffmpeg.setFfprobePath(ffprobePath);\r\n\r\nexport interface ExtractAudioOptions {\r\n /** Output format: 'mp3' (default, smaller) or 'wav' */\r\n format?: 'mp3' | 'wav';\r\n}\r\n\r\n/**\r\n * Extract audio from a video file to mono MP3 at 64kbps (small enough for Whisper).\r\n * A 10-minute video produces ~5MB MP3 vs ~115MB WAV.\r\n */\r\nexport async function extractAudio(\r\n videoPath: string,\r\n outputPath: string,\r\n options: ExtractAudioOptions = {},\r\n): Promise<string> {\r\n const { format = 'mp3' } = options;\r\n const outputDir = path.dirname(outputPath);\r\n await fs.mkdir(outputDir, { recursive: true });\r\n\r\n logger.info(`Extracting audio (${format}): ${videoPath} → ${outputPath}`);\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n const command = ffmpeg(videoPath).noVideo().audioChannels(1);\r\n\r\n if (format === 'mp3') {\r\n command.audioCodec('libmp3lame').audioBitrate('64k').audioFrequency(16000);\r\n } else {\r\n command.audioCodec('pcm_s16le').audioFrequency(16000);\r\n }\r\n\r\n command\r\n .output(outputPath)\r\n .on('end', () => {\r\n logger.info(`Audio extraction complete: ${outputPath}`);\r\n resolve(outputPath);\r\n })\r\n .on('error', (err) => {\r\n logger.error(`Audio extraction failed: ${err.message}`);\r\n reject(new Error(`Audio extraction failed: ${err.message}`));\r\n })\r\n .run();\r\n });\r\n}\r\n\r\n/**\r\n * Split an audio file into chunks of approximately `maxChunkSizeMB` each.\r\n * Uses ffmpeg to split by duration calculated from the file size.\r\n * Returns an array of chunk file paths.\r\n */\r\nexport async function splitAudioIntoChunks(\r\n audioPath: string,\r\n maxChunkSizeMB: number = 24,\r\n): Promise<string[]> {\r\n const stats = await fs.stat(audioPath);\r\n const fileSizeMB = stats.size / (1024 * 1024);\r\n\r\n if (fileSizeMB <= maxChunkSizeMB) {\r\n return [audioPath];\r\n }\r\n\r\n const duration = await getAudioDuration(audioPath);\r\n const numChunks = Math.ceil(fileSizeMB / maxChunkSizeMB);\r\n const chunkDuration = duration / numChunks;\r\n\r\n const ext = path.extname(audioPath);\r\n const base = audioPath.slice(0, -ext.length);\r\n const chunkPaths: string[] = [];\r\n\r\n logger.info(\r\n `Splitting ${fileSizeMB.toFixed(1)}MB audio into ${numChunks} chunks ` +\r\n `(~${chunkDuration.toFixed(0)}s each)`\r\n );\r\n\r\n for (let i = 0; i < numChunks; i++) {\r\n const startTime = i * chunkDuration;\r\n const chunkPath = `${base}_chunk${i}${ext}`;\r\n chunkPaths.push(chunkPath);\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n const cmd = ffmpeg(audioPath)\r\n .setStartTime(startTime)\r\n .setDuration(chunkDuration)\r\n .audioCodec('copy')\r\n .output(chunkPath)\r\n .on('end', () => resolve())\r\n .on('error', (err) => reject(new Error(`Chunk split failed: ${err.message}`)));\r\n cmd.run();\r\n });\r\n\r\n logger.info(`Created chunk ${i + 1}/${numChunks}: ${chunkPath}`);\r\n }\r\n\r\n return chunkPaths;\r\n}\r\n\r\n/** Get the duration of an audio file in seconds using ffprobe. */\r\nfunction getAudioDuration(audioPath: string): Promise<number> {\r\n return new Promise((resolve, reject) => {\r\n ffmpeg.ffprobe(audioPath, (err, metadata) => {\r\n if (err) return reject(new Error(`ffprobe failed: ${err.message}`));\r\n resolve(metadata.format.duration ?? 0);\r\n });\r\n });\r\n}\r\n","import OpenAI from 'openai'\r\nimport fs from 'fs'\r\nimport { getConfig } from '../../config/environment'\r\nimport logger from '../../config/logger'\r\nimport { getWhisperPrompt } from '../../config/brand'\r\nimport { Transcript, Segment, Word } from '../../types'\r\n\r\nconst MAX_FILE_SIZE_MB = 25\r\nconst WARN_FILE_SIZE_MB = 20\r\n\r\nexport async function transcribeAudio(audioPath: string): Promise<Transcript> {\r\n logger.info(`Starting Whisper transcription: ${audioPath}`)\r\n\r\n if (!fs.existsSync(audioPath)) {\r\n throw new Error(`Audio file not found: ${audioPath}`)\r\n }\r\n\r\n // Check file size against Whisper's 25MB limit\r\n const stats = fs.statSync(audioPath)\r\n const fileSizeMB = stats.size / (1024 * 1024)\r\n\r\n if (fileSizeMB > MAX_FILE_SIZE_MB) {\r\n throw new Error(\r\n `Audio file exceeds Whisper's 25MB limit (${fileSizeMB.toFixed(1)}MB). ` +\r\n 'The file should be split into smaller chunks before transcription.'\r\n )\r\n }\r\n if (fileSizeMB > WARN_FILE_SIZE_MB) {\r\n logger.warn(`Audio file is ${fileSizeMB.toFixed(1)}MB — approaching 25MB limit`)\r\n }\r\n\r\n const config = getConfig()\r\n const openai = new OpenAI({ apiKey: config.OPENAI_API_KEY })\r\n\r\n try {\r\n const prompt = getWhisperPrompt()\r\n const response = await openai.audio.transcriptions.create({\r\n model: 'whisper-1',\r\n file: fs.createReadStream(audioPath),\r\n response_format: 'verbose_json',\r\n timestamp_granularities: ['word', 'segment'],\r\n ...(prompt && { prompt }),\r\n })\r\n\r\n // The verbose_json response includes segments and words at the top level,\r\n // but the OpenAI SDK types don't expose them — cast to access raw fields.\r\n const verboseResponse = response as unknown as Record<string, unknown>\r\n const rawSegments = (verboseResponse.segments ?? []) as Array<{\r\n id: number; text: string; start: number; end: number\r\n }>\r\n const rawWords = (verboseResponse.words ?? []) as Array<{\r\n word: string; start: number; end: number\r\n }>\r\n\r\n const words: Word[] = rawWords.map((w) => ({\r\n word: w.word,\r\n start: w.start,\r\n end: w.end,\r\n }))\r\n\r\n const segments: Segment[] = rawSegments.map((s) => ({\r\n id: s.id,\r\n text: s.text.trim(),\r\n start: s.start,\r\n end: s.end,\r\n words: rawWords\r\n .filter((w) => w.start >= s.start && w.end <= s.end)\r\n .map((w) => ({ word: w.word, start: w.start, end: w.end })),\r\n }))\r\n\r\n logger.info(\r\n `Transcription complete — ${segments.length} segments, ` +\r\n `${words.length} words, language=${response.language}`\r\n )\r\n\r\n return {\r\n text: response.text,\r\n segments,\r\n words,\r\n language: response.language ?? 'unknown',\r\n duration: response.duration ?? 0,\r\n }\r\n } catch (error: unknown) {\r\n const message = error instanceof Error ? error.message : String(error)\r\n logger.error(`Whisper transcription failed: ${message}`)\r\n\r\n // OpenAI SDK errors expose a `status` property for HTTP status codes\r\n const status = (error as { status?: number }).status\r\n if (status === 401) {\r\n throw new Error('OpenAI API authentication failed. Check your OPENAI_API_KEY.')\r\n }\r\n if (status === 429) {\r\n throw new Error('OpenAI API rate limit exceeded. Please try again later.')\r\n }\r\n throw new Error(`Whisper transcription failed: ${message}`)\r\n }\r\n}\r\n","import fs from 'fs'\r\nimport path from 'path'\r\nimport { getConfig } from './environment'\r\nimport logger from './logger'\r\n\r\nexport interface BrandConfig {\r\n name: string\r\n handle: string\r\n tagline: string\r\n voice: {\r\n tone: string\r\n personality: string\r\n style: string\r\n }\r\n advocacy: {\r\n primary: string[]\r\n interests: string[]\r\n avoids: string[]\r\n }\r\n customVocabulary: string[]\r\n hashtags: {\r\n always: string[]\r\n preferred: string[]\r\n platforms: Record<string, string[]>\r\n }\r\n contentGuidelines: {\r\n shortsFocus: string\r\n blogFocus: string\r\n socialFocus: string\r\n }\r\n}\r\n\r\nconst defaultBrand: BrandConfig = {\r\n name: 'Creator',\r\n handle: '@creator',\r\n tagline: '',\r\n voice: {\r\n tone: 'professional, friendly',\r\n personality: 'A knowledgeable content creator.',\r\n style: 'Clear and concise.',\r\n },\r\n advocacy: {\r\n primary: [],\r\n interests: [],\r\n avoids: [],\r\n },\r\n customVocabulary: [],\r\n hashtags: {\r\n always: [],\r\n preferred: [],\r\n platforms: {},\r\n },\r\n contentGuidelines: {\r\n shortsFocus: 'Highlight key moments and insights.',\r\n blogFocus: 'Educational and informative content.',\r\n socialFocus: 'Engaging and authentic posts.',\r\n },\r\n}\r\n\r\nlet cachedBrand: BrandConfig | null = null\r\n\r\n/** Validate brand config and log warnings for missing or empty fields. */\r\nfunction validateBrandConfig(brand: Partial<BrandConfig>): void {\r\n const requiredStrings: (keyof BrandConfig)[] = ['name', 'handle', 'tagline']\r\n for (const field of requiredStrings) {\r\n if (!brand[field]) {\r\n logger.warn(`brand.json: missing or empty field \"${field}\"`)\r\n }\r\n }\r\n\r\n const requiredObjects: { key: keyof BrandConfig; subKeys: string[] }[] = [\r\n { key: 'voice', subKeys: ['tone', 'personality', 'style'] },\r\n { key: 'advocacy', subKeys: ['primary', 'interests'] },\r\n { key: 'hashtags', subKeys: ['always', 'preferred'] },\r\n { key: 'contentGuidelines', subKeys: ['shortsFocus', 'blogFocus', 'socialFocus'] },\r\n ]\r\n\r\n for (const { key, subKeys } of requiredObjects) {\r\n if (!brand[key]) {\r\n logger.warn(`brand.json: missing section \"${key}\"`)\r\n } else {\r\n const section = brand[key] as Record<string, unknown>\r\n for (const sub of subKeys) {\r\n if (!section[sub] || (Array.isArray(section[sub]) && (section[sub] as unknown[]).length === 0)) {\r\n logger.warn(`brand.json: missing or empty field \"${key}.${sub}\"`)\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!brand.customVocabulary || brand.customVocabulary.length === 0) {\r\n logger.warn('brand.json: \"customVocabulary\" is empty — Whisper prompt will be blank')\r\n }\r\n}\r\n\r\nexport function getBrandConfig(): BrandConfig {\r\n if (cachedBrand) return cachedBrand\r\n\r\n const config = getConfig()\r\n const brandPath = config.BRAND_PATH\r\n\r\n if (!fs.existsSync(brandPath)) {\r\n logger.warn('brand.json not found — using defaults')\r\n cachedBrand = { ...defaultBrand }\r\n return cachedBrand\r\n }\r\n\r\n const raw = fs.readFileSync(brandPath, 'utf-8')\r\n cachedBrand = JSON.parse(raw) as BrandConfig\r\n validateBrandConfig(cachedBrand)\r\n logger.info(`Brand config loaded: ${cachedBrand.name}`)\r\n return cachedBrand\r\n}\r\n\r\n// Helper to get Whisper prompt from brand vocabulary\r\nexport function getWhisperPrompt(): string {\r\n const brand = getBrandConfig()\r\n return brand.customVocabulary.join(', ')\r\n}\r\n","import path from 'path'\r\nimport fsp from 'fs/promises'\r\nimport { VideoFile, Transcript } from '../types'\r\nimport { generateSRT, generateVTT, generateStyledASS } from '../tools/captions/captionGenerator'\r\nimport { getConfig } from '../config/environment'\r\nimport logger from '../config/logger'\r\n\r\n/**\r\n * Generate SRT, VTT, and ASS caption files for a video and save them to disk.\r\n * Returns the list of written file paths.\r\n */\r\nexport async function generateCaptions(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n): Promise<string[]> {\r\n const config = getConfig()\r\n const captionsDir = path.join(config.OUTPUT_DIR, video.slug, 'captions')\r\n await fsp.mkdir(captionsDir, { recursive: true })\r\n\r\n const srtPath = path.join(captionsDir, 'captions.srt')\r\n const vttPath = path.join(captionsDir, 'captions.vtt')\r\n const assPath = path.join(captionsDir, 'captions.ass')\r\n\r\n const srt = generateSRT(transcript)\r\n const vtt = generateVTT(transcript)\r\n const ass = generateStyledASS(transcript)\r\n\r\n await Promise.all([\r\n fsp.writeFile(srtPath, srt, 'utf-8'),\r\n fsp.writeFile(vttPath, vtt, 'utf-8'),\r\n fsp.writeFile(assPath, ass, 'utf-8'),\r\n ])\r\n\r\n const paths = [srtPath, vttPath, assPath]\r\n logger.info(`Captions saved: ${paths.join(', ')}`)\r\n return paths\r\n}\r\n","/**\r\n * Caption generator for the Advanced SubStation Alpha (ASS) subtitle format.\r\n *\r\n * ### Why ASS instead of SRT/VTT?\r\n * ASS supports inline style overrides — font size, color, and animation per\r\n * character/word — which enables the \"active word pop\" karaoke effect used\r\n * in modern short-form video (TikTok, Reels). SRT and VTT only support\r\n * plain text or basic HTML tags with no per-word timing control.\r\n *\r\n * ### Karaoke word highlighting approach\r\n * Instead of ASS's native `\\k` karaoke tags (which highlight left-to-right\r\n * within a line), we generate **one Dialogue line per word-state**. Each line\r\n * renders the entire caption group but with the currently-spoken word in a\r\n * different color and size. Contiguous end/start timestamps between\r\n * word-states prevent flicker. This gives us full control over the visual\r\n * treatment (color, font-size, scale animations) without the limitations\r\n * of the `\\k` tag.\r\n *\r\n * @module captionGenerator\r\n */\r\n\r\nimport { Transcript, Segment, Word, CaptionStyle } from '../../types'\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Pad a number to a fixed width with leading zeros. */\r\nfunction pad(n: number, width: number): string {\r\n return String(n).padStart(width, '0')\r\n}\r\n\r\n/** Convert seconds → SRT timestamp \"HH:MM:SS,mmm\" */\r\nfunction toSRT(seconds: number): string {\r\n const h = Math.floor(seconds / 3600)\r\n const m = Math.floor((seconds % 3600) / 60)\r\n const s = Math.floor(seconds % 60)\r\n const ms = Math.round((seconds - Math.floor(seconds)) * 1000)\r\n return `${pad(h, 2)}:${pad(m, 2)}:${pad(s, 2)},${pad(ms, 3)}`\r\n}\r\n\r\n/** Convert seconds → VTT timestamp \"HH:MM:SS.mmm\" */\r\nfunction toVTT(seconds: number): string {\r\n return toSRT(seconds).replace(',', '.')\r\n}\r\n\r\n/** Convert seconds → ASS timestamp \"H:MM:SS.cc\" */\r\nfunction toASS(seconds: number): string {\r\n const h = Math.floor(seconds / 3600)\r\n const m = Math.floor((seconds % 3600) / 60)\r\n const s = Math.floor(seconds % 60)\r\n const cs = Math.round((seconds - Math.floor(seconds)) * 100)\r\n return `${h}:${pad(m, 2)}:${pad(s, 2)}.${pad(cs, 2)}`\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Premium caption constants\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Silence gap threshold in seconds – gaps longer than this split caption groups. */\r\nconst SILENCE_GAP_THRESHOLD = 0.8\r\n/** Maximum words displayed simultaneously in a caption group. */\r\nconst MAX_WORDS_PER_GROUP = 8\r\n/** Target words per display line within a group (splits into 2 lines above this). */\r\nconst WORDS_PER_LINE = 4\r\n/** ASS BGR color for the active (currently-spoken) word – yellow. */\r\nconst ACTIVE_COLOR = '\\\\c&H00FFFF&'\r\n/** ASS BGR color for inactive words – white. */\r\nconst BASE_COLOR = '\\\\c&HFFFFFF&'\r\n/** Font size for the active word. */\r\nconst ACTIVE_FONT_SIZE = 54\r\n/** Font size for inactive words (matches style default). */\r\nconst BASE_FONT_SIZE = 42\r\n\r\n// ---------------------------------------------------------------------------\r\n// Medium caption constants (smaller, bottom-positioned for longer content)\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Font size for the active word in medium style. */\r\nconst MEDIUM_ACTIVE_FONT_SIZE = 40\r\n/** Font size for inactive words in medium style. */\r\nconst MEDIUM_BASE_FONT_SIZE = 32\r\n\r\n// ---------------------------------------------------------------------------\r\n// Portrait caption constants (Opus Clips style)\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Font size for the active word in portrait style. */\r\nconst PORTRAIT_ACTIVE_FONT_SIZE = 78\r\n/** Font size for inactive words in portrait style. */\r\nconst PORTRAIT_BASE_FONT_SIZE = 66\r\n/** ASS BGR color for the active word in portrait style – green. */\r\nconst PORTRAIT_ACTIVE_COLOR = '\\\\c&H00FF00&'\r\n/** ASS BGR color for inactive words in portrait style – white. */\r\nconst PORTRAIT_BASE_COLOR = '\\\\c&HFFFFFF&'\r\n\r\n// ---------------------------------------------------------------------------\r\n// SRT (segment-level)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function generateSRT(transcript: Transcript): string {\r\n return transcript.segments\r\n .map((seg: Segment, i: number) => {\r\n const idx = i + 1\r\n const start = toSRT(seg.start)\r\n const end = toSRT(seg.end)\r\n const text = seg.text.trim()\r\n return `${idx}\\n${start} --> ${end}\\n${text}`\r\n })\r\n .join('\\n\\n')\r\n .concat('\\n')\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// VTT (segment-level)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function generateVTT(transcript: Transcript): string {\r\n const cues = transcript.segments\r\n .map((seg: Segment) => {\r\n const start = toVTT(seg.start)\r\n const end = toVTT(seg.end)\r\n const text = seg.text.trim()\r\n return `${start} --> ${end}\\n${text}`\r\n })\r\n .join('\\n\\n')\r\n\r\n return `WEBVTT\\n\\n${cues}\\n`\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// ASS – Premium active-word-pop captions\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * ASS header for landscape (16:9, 1920×1080) captions.\r\n *\r\n * ### Style fields explained (comma-separated in the Style line):\r\n * - `Fontname: Montserrat` — bundled with the project; FFmpeg's `ass` filter\r\n * uses `fontsdir=.` so libass finds the .ttf files next to the .ass file.\r\n * - `Fontsize: 42` — base size for inactive words\r\n * - `PrimaryColour: &H00FFFFFF` — white (ASS uses `&HAABBGGRR` — alpha, blue, green, red)\r\n * - `OutlineColour: &H00000000` — black outline for readability on any background\r\n * - `BackColour: &H80000000` — 50% transparent black shadow\r\n * - `Bold: 1` — bold for better readability at small sizes\r\n * - `BorderStyle: 1` — outline + drop shadow (not opaque box)\r\n * - `Outline: 3` — 3px outline thickness\r\n * - `Shadow: 1` — 1px drop shadow\r\n * - `Alignment: 2` — bottom-center (SSA alignment: 1=left, 2=center, 3=right;\r\n * add 4 for top, 8 for middle — so 2 = bottom-center)\r\n * - `MarginV: 40` — 40px above the bottom edge\r\n * - `WrapStyle: 0` — smart word wrap\r\n */\r\nconst ASS_HEADER = `[Script Info]\r\nTitle: Auto-generated captions\r\nScriptType: v4.00+\r\nPlayResX: 1920\r\nPlayResY: 1080\r\nWrapStyle: 0\r\n\r\n[V4+ Styles]\r\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\nStyle: Default,Montserrat,42,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,20,20,40,1\r\n\r\n[Events]\r\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n`\r\n\r\n/**\r\n * ASS header for portrait (9:16, 1080×1920) captions — used for shorts.\r\n *\r\n * Key differences from the landscape header:\r\n * - `PlayResX/Y: 1080×1920` — matches portrait video dimensions\r\n * - `Fontsize: 78` — larger base font for vertical video viewing (small screens)\r\n * - `MarginV: 700` — pushes captions to the center-bottom area, leaving room\r\n * for the hook overlay at the top\r\n * - Includes a `Hook` style: semi-transparent pill/badge background\r\n * (`BorderStyle: 3` = opaque box) for the opening hook text overlay\r\n */\r\nconst ASS_HEADER_PORTRAIT = `[Script Info]\r\nTitle: Auto-generated captions\r\nScriptType: v4.00+\r\nPlayResX: 1080\r\nPlayResY: 1920\r\nWrapStyle: 0\r\n\r\n[V4+ Styles]\r\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\nStyle: Default,Montserrat,78,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,30,30,700,1\r\nStyle: Hook,Montserrat,56,&H00333333,&H00333333,&H60D0D0D0,&H60E0E0E0,1,0,0,0,100,100,2,0,3,18,2,8,80,80,60,1\r\n\r\n[Events]\r\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n`\r\n\r\n/**\r\n * ASS header for medium-style captions (1920×1080 but smaller font).\r\n *\r\n * Used for longer clips where large captions would be distracting.\r\n * - `Fontsize: 32` — smaller than the shorts style\r\n * - `Alignment: 2` — bottom-center\r\n * - `MarginV: 60` — slightly higher from the bottom edge to avoid UI overlaps\r\n */\r\nconst ASS_HEADER_MEDIUM = `[Script Info]\r\nTitle: Auto-generated captions\r\nScriptType: v4.00+\r\nPlayResX: 1920\r\nPlayResY: 1080\r\nWrapStyle: 0\r\n\r\n[V4+ Styles]\r\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\nStyle: Default,Montserrat,32,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,2,1,2,20,20,60,1\r\n\r\n[Events]\r\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n`\r\n\r\n/**\r\n * Group words into caption groups split on silence gaps and max word count.\r\n * All words within a group are displayed simultaneously; captions disappear\r\n * entirely during gaps longer than SILENCE_GAP_THRESHOLD.\r\n */\r\nfunction groupWordsBySpeech(words: Word[]): Word[][] {\r\n if (words.length === 0) return []\r\n\r\n const groups: Word[][] = []\r\n let current: Word[] = []\r\n\r\n for (let i = 0; i < words.length; i++) {\r\n current.push(words[i])\r\n\r\n const isLast = i === words.length - 1\r\n const hasGap =\r\n !isLast && words[i + 1].start - words[i].end > SILENCE_GAP_THRESHOLD\r\n const atMax = current.length >= MAX_WORDS_PER_GROUP\r\n\r\n if (isLast || hasGap || atMax) {\r\n groups.push(current)\r\n current = []\r\n }\r\n }\r\n\r\n return groups\r\n}\r\n\r\n/**\r\n * Split a caption group into 1–2 display lines.\r\n * Groups with ≤ WORDS_PER_LINE words stay on one line; larger groups\r\n * are split at the midpoint into two lines joined with \\\\N.\r\n */\r\nfunction splitGroupIntoLines(group: Word[]): Word[][] {\r\n if (group.length <= WORDS_PER_LINE) return [group]\r\n const mid = Math.ceil(group.length / 2)\r\n return [group.slice(0, mid), group.slice(mid)]\r\n}\r\n\r\n/**\r\n * Build premium ASS dialogue lines with active-word highlighting.\r\n * Generates one Dialogue line per word-state: the full caption group is\r\n * rendered with the currently-spoken word in yellow at a larger size while\r\n * all other words stay white at the base size. Contiguous end/start times\r\n * between word-states prevent flicker.\r\n */\r\nfunction buildPremiumDialogueLines(words: Word[], style: CaptionStyle = 'shorts'): string[] {\r\n const activeFontSize = style === 'portrait' ? PORTRAIT_ACTIVE_FONT_SIZE\r\n : style === 'medium' ? MEDIUM_ACTIVE_FONT_SIZE : ACTIVE_FONT_SIZE\r\n const baseFontSize = style === 'portrait' ? PORTRAIT_BASE_FONT_SIZE\r\n : style === 'medium' ? MEDIUM_BASE_FONT_SIZE : BASE_FONT_SIZE\r\n const groups = groupWordsBySpeech(words)\r\n const dialogues: string[] = []\r\n\r\n for (const group of groups) {\r\n const displayLines = splitGroupIntoLines(group)\r\n\r\n for (let activeIdx = 0; activeIdx < group.length; activeIdx++) {\r\n const activeWord = group[activeIdx]\r\n\r\n // Contiguous timing: end = next word's start, or this word's own end\r\n const endTime =\r\n activeIdx < group.length - 1\r\n ? group[activeIdx + 1].start\r\n : activeWord.end\r\n\r\n // Render all words across display lines with the active word highlighted\r\n const renderedLines: string[] = []\r\n let globalIdx = 0\r\n\r\n for (const line of displayLines) {\r\n const rendered = line.map((w) => {\r\n const idx = globalIdx++\r\n const text = w.word.trim()\r\n if (idx === activeIdx) {\r\n if (style === 'portrait') {\r\n // Opus Clips style: green color + scale pop animation\r\n return `{${PORTRAIT_ACTIVE_COLOR}\\\\fs${activeFontSize}\\\\fscx130\\\\fscy130\\\\t(0,150,\\\\fscx100\\\\fscy100)}${text}`\r\n }\r\n return `{${ACTIVE_COLOR}\\\\fs${activeFontSize}}${text}`\r\n }\r\n if (style === 'portrait') {\r\n return `{${PORTRAIT_BASE_COLOR}\\\\fs${baseFontSize}}${text}`\r\n }\r\n return `{${BASE_COLOR}\\\\fs${baseFontSize}}${text}`\r\n })\r\n renderedLines.push(rendered.join(' '))\r\n }\r\n\r\n const text = renderedLines.join('\\\\N')\r\n dialogues.push(\r\n `Dialogue: 0,${toASS(activeWord.start)},${toASS(endTime)},Default,,0,0,0,,${text}`,\r\n )\r\n }\r\n }\r\n\r\n return dialogues\r\n}\r\n\r\n/**\r\n * Generate premium ASS captions with active-word-pop highlighting.\r\n *\r\n * ### How it works\r\n * Words are grouped by speech bursts (split on silence gaps > 0.8s or after\r\n * 8 words). Within each group, one Dialogue line is emitted per word — the\r\n * full group is shown each time, but the \"active\" word gets a different color\r\n * and larger font size. This creates a karaoke-style bounce effect.\r\n *\r\n * @param transcript - Full transcript with word-level timestamps\r\n * @param style - Visual style: 'shorts' (large centered), 'medium' (small bottom),\r\n * or 'portrait' (Opus Clips style with green highlight + scale animation)\r\n * @returns Complete ASS file content (header + dialogue lines)\r\n */\r\nexport function generateStyledASS(transcript: Transcript, style: CaptionStyle = 'shorts'): string {\r\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\r\n const allWords = transcript.words\r\n if (allWords.length === 0) return header\r\n\r\n return header + buildPremiumDialogueLines(allWords, style).join('\\n') + '\\n'\r\n}\r\n\r\n/**\r\n * Generate premium ASS captions for a single contiguous segment.\r\n * Filters words within [startTime, endTime] (plus buffer), adjusts timestamps\r\n * relative to the clip's buffered start so they align with the extracted video.\r\n */\r\nexport function generateStyledASSForSegment(\r\n transcript: Transcript,\r\n startTime: number,\r\n endTime: number,\r\n buffer: number = 1.0,\r\n style: CaptionStyle = 'shorts',\r\n): string {\r\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\r\n const bufferedStart = Math.max(0, startTime - buffer)\r\n const bufferedEnd = endTime + buffer\r\n\r\n const words = transcript.words.filter(\r\n (w) => w.start >= bufferedStart && w.end <= bufferedEnd,\r\n )\r\n if (words.length === 0) return header\r\n\r\n const adjusted: Word[] = words.map((w) => ({\r\n word: w.word,\r\n start: w.start - bufferedStart,\r\n end: w.end - bufferedStart,\r\n }))\r\n\r\n return header + buildPremiumDialogueLines(adjusted, style).join('\\n') + '\\n'\r\n}\r\n\r\n/**\r\n * Generate premium ASS captions for a composite clip made of multiple segments.\r\n * Each segment's words are extracted and remapped to the concatenated timeline,\r\n * accounting for the buffer added during clip extraction.\r\n */\r\nexport function generateStyledASSForComposite(\r\n transcript: Transcript,\r\n segments: { start: number; end: number }[],\r\n buffer: number = 1.0,\r\n style: CaptionStyle = 'shorts',\r\n): string {\r\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\r\n const allAdjusted: Word[] = []\r\n let runningOffset = 0\r\n\r\n for (const seg of segments) {\r\n const bufferedStart = Math.max(0, seg.start - buffer)\r\n const bufferedEnd = seg.end + buffer\r\n const segDuration = bufferedEnd - bufferedStart\r\n\r\n const words = transcript.words.filter(\r\n (w) => w.start >= bufferedStart && w.end <= bufferedEnd,\r\n )\r\n\r\n for (const w of words) {\r\n allAdjusted.push({\r\n word: w.word,\r\n start: w.start - bufferedStart + runningOffset,\r\n end: w.end - bufferedStart + runningOffset,\r\n })\r\n }\r\n\r\n runningOffset += segDuration\r\n }\r\n\r\n if (allAdjusted.length === 0) return header\r\n\r\n return header + buildPremiumDialogueLines(allAdjusted, style).join('\\n') + '\\n'\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Hook text overlay for portrait shorts\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Maximum characters for hook text before truncation. */\r\nconst HOOK_TEXT_MAX_LENGTH = 60\r\n\r\n/**\r\n * Generate ASS dialogue lines for a hook text overlay at the top of the video.\r\n *\r\n * The hook is a short attention-grabbing phrase (e.g. \"Here's why you should\r\n * learn TypeScript\") displayed as a translucent pill/badge at the top of a\r\n * portrait video for the first few seconds.\r\n *\r\n * Uses the `Hook` style defined in {@link ASS_HEADER_PORTRAIT} which has\r\n * `BorderStyle: 3` (opaque box background) and `Alignment: 8` (top-center).\r\n *\r\n * The `\\fad(300,500)` tag creates a 300ms fade-in and 500ms fade-out so the\r\n * hook doesn't appear/disappear abruptly.\r\n *\r\n * @param hookText - The attention-grabbing phrase (truncated to 60 chars)\r\n * @param displayDuration - How long to show the hook in seconds (default: 4s)\r\n * @param _style - Caption style (currently only 'portrait' uses hooks)\r\n * @returns A single ASS Dialogue line to append to the Events section\r\n */\r\nexport function generateHookOverlay(\r\n hookText: string,\r\n displayDuration: number = 4.0,\r\n _style: CaptionStyle = 'portrait',\r\n): string {\r\n const text =\r\n hookText.length > HOOK_TEXT_MAX_LENGTH\r\n ? hookText.slice(0, HOOK_TEXT_MAX_LENGTH - 3) + '...'\r\n : hookText\r\n\r\n return `Dialogue: 1,${toASS(0)},${toASS(displayDuration)},Hook,,0,0,0,,{\\\\fad(300,500)}${text}`\r\n}\r\n\r\n/**\r\n * Generate a complete portrait ASS file with captions AND hook text overlay.\r\n */\r\nexport function generatePortraitASSWithHook(\r\n transcript: Transcript,\r\n hookText: string,\r\n startTime: number,\r\n endTime: number,\r\n buffer?: number,\r\n): string {\r\n const baseASS = generateStyledASSForSegment(transcript, startTime, endTime, buffer, 'portrait')\r\n const hookLine = generateHookOverlay(hookText, 4.0, 'portrait')\r\n return baseASS + hookLine + '\\n'\r\n}\r\n\r\n/**\r\n * Generate a complete portrait ASS file for a composite clip with captions AND hook text overlay.\r\n */\r\nexport function generatePortraitASSWithHookComposite(\r\n transcript: Transcript,\r\n segments: { start: number; end: number }[],\r\n hookText: string,\r\n buffer?: number,\r\n): string {\r\n const baseASS = generateStyledASSForComposite(transcript, segments, buffer, 'portrait')\r\n const hookLine = generateHookOverlay(hookText, 4.0, 'portrait')\r\n return baseASS + hookLine + '\\n'\r\n}\r\n","import type { ToolWithHandler } from '../providers/types.js'\r\nimport { promises as fs } from 'fs'\r\nimport path from 'path'\r\n\r\nimport { BaseAgent } from './BaseAgent'\r\nimport { captureFrame } from '../tools/ffmpeg/frameCapture'\r\nimport logger from '../config/logger'\r\nimport { getBrandConfig } from '../config/brand'\r\nimport { getConfig } from '../config/environment'\r\nimport type { VideoFile, Transcript, VideoSummary, VideoSnapshot, ShortClip, Chapter } from '../types'\r\n\r\n// ── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\n/** Format seconds → \"MM:SS\" */\r\nfunction fmtTime(seconds: number): string {\r\n const m = Math.floor(seconds / 60)\r\n const s = Math.floor(seconds % 60)\r\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\r\n}\r\n\r\n/** Build a compact transcript block with timestamps for the LLM prompt. */\r\nfunction buildTranscriptBlock(transcript: Transcript): string {\r\n return transcript.segments\r\n .map((seg) => `[${fmtTime(seg.start)} → ${fmtTime(seg.end)}] ${seg.text.trim()}`)\r\n .join('\\n')\r\n}\r\n\r\n// ── System prompt ────────────────────────────────────────────────────────────\r\n\r\nfunction buildSystemPrompt(shortsInfo: string, socialPostsInfo: string, captionsInfo: string, chaptersInfo: string): string {\r\n const brand = getBrandConfig()\r\n\r\n return `You are a Video Summary Agent writing from the perspective of ${brand.name} (${brand.handle}).\r\nBrand voice: ${brand.voice.tone}. ${brand.voice.personality} ${brand.voice.style}\r\n\r\nYour job is to analyse a video transcript and produce a beautiful, narrative-style Markdown README.\r\n\r\n**Workflow**\r\n1. Read the transcript carefully.\r\n2. Identify 3-8 key topics, decisions, highlights, or memorable moments.\r\n3. For each highlight, decide on a representative timestamp and call the \"capture_frame\" tool to grab a screenshot.\r\n4. Once all frames are captured, call the \"write_summary\" tool with the final Markdown.\r\n\r\n**Markdown structure — follow this layout exactly:**\r\n\r\n\\`\\`\\`\r\n# [Video Title]\r\n\r\n> [Compelling one-line hook/tagline that captures the video's value]\r\n\r\n[2-3 paragraph natural summary that reads like a blog post, NOT a timeline.\r\nWeave in key insights naturally. Write in the brand voice: ${brand.voice.tone}.\r\n${brand.contentGuidelines.blogFocus}]\r\n\r\n---\r\n\r\n## Key Moments\r\n\r\n[For each key topic: write a narrative paragraph (not bullet points).\r\nEmbed the timestamp as an inline badge like \\`[0:12]\\` within the text, NOT as a section header.\r\nEmbed the screenshot naturally within or after the paragraph.\r\nUse blockquotes (>) for standout quotes or insights.]\r\n\r\n![Description](thumbnails/snapshot-001.png)\r\n\r\n[Continue with next topic paragraph...]\r\n\r\n---\r\n\r\n## 📊 Quick Reference\r\n\r\n| Topic | Timestamp |\r\n|-------|-----------|\r\n| Topic name | \\`M:SS\\` |\r\n| ... | ... |\r\n\r\n---\r\n${chaptersInfo}\r\n${shortsInfo}\r\n${socialPostsInfo}\r\n${captionsInfo}\r\n\r\n---\r\n\r\n*Generated on [DATE] • Duration: [DURATION] • Tags: [relevant tags]*\r\n\\`\\`\\`\r\n\r\n**Writing style rules**\r\n- Write in a narrative, blog-post style — NOT a timestamp-driven timeline.\r\n- Timestamps appear as subtle inline badges like \\`[0:12]\\` or \\`[1:30]\\` within sentences, never as section headers.\r\n- The summary paragraphs should flow naturally and be enjoyable to read.\r\n- Use the brand perspective: ${brand.voice.personality}\r\n- Topics to emphasize: ${brand.advocacy.interests.join(', ')}\r\n- Avoid: ${brand.advocacy.avoids.join(', ')}\r\n\r\n**Screenshot distribution rules — CRITICAL**\r\n- You MUST spread screenshots across the ENTIRE video duration, from beginning to end.\r\n- Divide the video into equal segments based on the number of screenshots you plan to capture, and pick one timestamp from each segment.\r\n- NO MORE than 2 screenshots should fall within the same 60-second window.\r\n- If the video is longer than 2 minutes, your first screenshot must NOT be in the first 10% and your last screenshot must be in the final 30% of the video.\r\n- Use the suggested timestamp ranges provided in the user message as guidance, but pick the exact moment within each range that best matches a key topic in the transcript.\r\n\r\n**Tool rules**\r\n- Always call \"capture_frame\" BEFORE \"write_summary\".\r\n- The snapshot index must be a 1-based integer; the filename will be snapshot-001.png, etc.\r\n- In the Markdown, reference screenshots as \\`thumbnails/snapshot-001.png\\` (relative path).\r\n- Call \"write_summary\" exactly once with the complete Markdown string.`\r\n}\r\n\r\n// ── Tool argument shapes ─────────────────────────────────────────────────────\r\n\r\ninterface CaptureFrameArgs {\r\n timestamp: number\r\n description: string\r\n index: number\r\n}\r\n\r\ninterface WriteSummaryArgs {\r\n markdown: string\r\n title: string\r\n overview: string\r\n keyTopics: string[]\r\n}\r\n\r\n// ── SummaryAgent ─────────────────────────────────────────────────────────────\r\n\r\nclass SummaryAgent extends BaseAgent {\r\n private videoPath: string\r\n private outputDir: string\r\n private snapshots: VideoSnapshot[] = []\r\n\r\n constructor(videoPath: string, outputDir: string, systemPrompt: string) {\r\n super('SummaryAgent', systemPrompt)\r\n this.videoPath = videoPath\r\n this.outputDir = outputDir\r\n }\r\n\r\n // Resolved paths\r\n private get thumbnailDir(): string {\r\n return path.join(this.outputDir, 'thumbnails')\r\n }\r\n\r\n private get markdownPath(): string {\r\n return path.join(this.outputDir, 'README.md')\r\n }\r\n\r\n /* ── Tools exposed to the LLM ─────────────────────────────────────────── */\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'capture_frame',\r\n description:\r\n 'Capture a screenshot from the video at a specific timestamp. ' +\r\n 'Provide: timestamp (seconds), description (what is shown), index (1-based integer for filename).',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n timestamp: { type: 'number', description: 'Timestamp in seconds to capture' },\r\n description: { type: 'string', description: 'Brief description of the visual moment' },\r\n index: { type: 'integer', description: '1-based snapshot index (used for filename)' },\r\n },\r\n required: ['timestamp', 'description', 'index'],\r\n },\r\n handler: async (rawArgs: unknown) => {\r\n const args = rawArgs as CaptureFrameArgs\r\n return this.handleCaptureFrame(args)\r\n },\r\n },\r\n {\r\n name: 'write_summary',\r\n description:\r\n 'Write the final Markdown summary to disk. ' +\r\n 'Provide: markdown (full README content), title, overview, and keyTopics array.',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n markdown: { type: 'string', description: 'Complete Markdown content for README.md' },\r\n title: { type: 'string', description: 'Video title' },\r\n overview: { type: 'string', description: 'Short overview paragraph' },\r\n keyTopics: {\r\n type: 'array',\r\n items: { type: 'string' },\r\n description: 'List of key topic names',\r\n },\r\n },\r\n required: ['markdown', 'title', 'overview', 'keyTopics'],\r\n },\r\n handler: async (rawArgs: unknown) => {\r\n const args = rawArgs as WriteSummaryArgs\r\n return this.handleWriteSummary(args)\r\n },\r\n },\r\n ]\r\n }\r\n\r\n /* ── Tool dispatch (required by BaseAgent) ─────────────────────────────── */\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n switch (toolName) {\r\n case 'capture_frame':\r\n return this.handleCaptureFrame(args as unknown as CaptureFrameArgs)\r\n case 'write_summary':\r\n return this.handleWriteSummary(args as unknown as WriteSummaryArgs)\r\n default:\r\n throw new Error(`Unknown tool: ${toolName}`)\r\n }\r\n }\r\n\r\n /* ── Tool implementations ──────────────────────────────────────────────── */\r\n\r\n private async handleCaptureFrame(args: CaptureFrameArgs): Promise<string> {\r\n const idx = String(args.index).padStart(3, '0')\r\n const filename = `snapshot-${idx}.png`\r\n const outputPath = path.join(this.thumbnailDir, filename)\r\n\r\n await captureFrame(this.videoPath, args.timestamp, outputPath)\r\n\r\n const snapshot: VideoSnapshot = {\r\n timestamp: args.timestamp,\r\n description: args.description,\r\n outputPath,\r\n }\r\n this.snapshots.push(snapshot)\r\n\r\n logger.info(`[SummaryAgent] Captured snapshot ${idx} at ${fmtTime(args.timestamp)}`)\r\n return `Frame captured: thumbnails/${filename}`\r\n }\r\n\r\n private async handleWriteSummary(args: WriteSummaryArgs): Promise<string> {\r\n await fs.mkdir(this.outputDir, { recursive: true })\r\n await fs.writeFile(this.markdownPath, args.markdown, 'utf-8')\r\n\r\n logger.info(`[SummaryAgent] Wrote summary → ${this.markdownPath}`)\r\n return `Summary written to ${this.markdownPath}`\r\n }\r\n\r\n /** Expose collected data after the run. */\r\n getResult(args: WriteSummaryArgs): VideoSummary {\r\n return {\r\n title: args.title,\r\n overview: args.overview,\r\n keyTopics: args.keyTopics,\r\n snapshots: this.snapshots,\r\n markdownPath: this.markdownPath,\r\n }\r\n }\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\n/** Build the Shorts section for the README template. */\r\nfunction buildShortsSection(shorts?: ShortClip[]): string {\r\n if (!shorts || shorts.length === 0) {\r\n return `\r\n## ✂️ Shorts\r\n\r\n| Short | Duration | Description |\r\n|-------|----------|-------------|\r\n| *Shorts will appear here once generated* | | |`\r\n }\r\n\r\n const rows = shorts\r\n .map((s) => `| [${s.title}](shorts/${s.slug}.mp4) | ${Math.round(s.totalDuration)}s | ${s.description} |`)\r\n .join('\\n')\r\n\r\n return `\r\n## ✂️ Shorts\r\n\r\n| Short | Duration | Description |\r\n|-------|----------|-------------|\r\n${rows}`\r\n}\r\n\r\n/** Build the Social Media Posts section for the README template. */\r\nfunction buildSocialPostsSection(): string {\r\n return `\r\n## 📱 Social Media Posts\r\n\r\n- [TikTok](social-posts/tiktok.md)\r\n- [YouTube](social-posts/youtube.md)\r\n- [Instagram](social-posts/instagram.md)\r\n- [LinkedIn](social-posts/linkedin.md)\r\n- [X / Twitter](social-posts/x.md)\r\n- [Dev.to Blog](social-posts/devto.md)`\r\n}\r\n\r\n/** Build the Captions section for the README template. */\r\nfunction buildCaptionsSection(): string {\r\n return `\r\n## 🎬 Captions\r\n\r\n- [SRT](captions/captions.srt) | [VTT](captions/captions.vtt) | [ASS (Styled)](captions/captions.ass)`\r\n}\r\n\r\n/** Format seconds → YouTube-style timestamp for chapters display */\r\nfunction toYouTubeTimestamp(seconds: number): string {\r\n const h = Math.floor(seconds / 3600)\r\n const m = Math.floor((seconds % 3600) / 60)\r\n const s = Math.floor(seconds % 60)\r\n return h > 0\r\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\r\n : `${m}:${String(s).padStart(2, '0')}`\r\n}\r\n\r\n/** Build the Chapters section for the README template. */\r\nfunction buildChaptersSection(chapters?: Chapter[]): string {\r\n if (!chapters || chapters.length === 0) {\r\n return ''\r\n }\r\n\r\n const rows = chapters\r\n .map((ch) => `| \\`${toYouTubeTimestamp(ch.timestamp)}\\` | ${ch.title} | ${ch.description} |`)\r\n .join('\\n')\r\n\r\n return `\r\n## 📑 Chapters\r\n\r\n| Time | Chapter | Description |\r\n|------|---------|-------------|\r\n${rows}\r\n\r\n> 📋 [YouTube Timestamps](chapters/chapters-youtube.txt) • [Markdown](chapters/chapters.md) • [JSON](chapters/chapters.json)`\r\n}\r\n\r\n/**\r\n * Generate a beautiful Markdown summary for a video recording.\r\n *\r\n * 1. Creates a SummaryAgent with `capture_frame` and `write_summary` tools\r\n * 2. Builds a prompt containing the full transcript with timestamps\r\n * 3. Lets the agent analyse the transcript, capture key frames, and write Markdown\r\n * 4. Returns a {@link VideoSummary} with metadata and snapshot paths\r\n */\r\nexport async function generateSummary(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n shorts?: ShortClip[],\r\n chapters?: Chapter[],\r\n): Promise<VideoSummary> {\r\n const config = getConfig()\r\n const outputDir = path.join(config.OUTPUT_DIR, video.slug)\r\n\r\n // Build content-section snippets for the system prompt\r\n const shortsInfo = buildShortsSection(shorts)\r\n const socialPostsInfo = buildSocialPostsSection()\r\n const captionsInfo = buildCaptionsSection()\r\n const chaptersInfo = buildChaptersSection(chapters)\r\n\r\n const systemPrompt = buildSystemPrompt(shortsInfo, socialPostsInfo, captionsInfo, chaptersInfo)\r\n const agent = new SummaryAgent(video.repoPath, outputDir, systemPrompt)\r\n\r\n const transcriptBlock = buildTranscriptBlock(transcript)\r\n\r\n // Pre-calculate suggested screenshot time ranges spread across the full video\r\n const screenshotCount = Math.min(8, Math.max(3, Math.round(video.duration / 120)))\r\n const interval = video.duration / screenshotCount\r\n const suggestedRanges = Array.from({ length: screenshotCount }, (_, i) => {\r\n const center = Math.round(interval * (i + 0.5))\r\n const lo = Math.max(0, Math.round(center - interval / 2))\r\n const hi = Math.min(Math.round(video.duration), Math.round(center + interval / 2))\r\n return `${fmtTime(lo)}–${fmtTime(hi)} (${lo}s–${hi}s)`\r\n }).join(', ')\r\n\r\n const userPrompt = [\r\n `**Video:** ${video.filename}`,\r\n `**Duration:** ${fmtTime(video.duration)} (${Math.round(video.duration)} seconds)`,\r\n `**Date:** ${video.createdAt.toISOString().slice(0, 10)}`,\r\n '',\r\n `**Suggested screenshot time ranges (one screenshot per range):**`,\r\n suggestedRanges,\r\n '',\r\n '---',\r\n '',\r\n '**Transcript:**',\r\n '',\r\n transcriptBlock,\r\n ].join('\\n')\r\n\r\n let lastWriteArgs: WriteSummaryArgs | undefined\r\n\r\n // Intercept write_summary args so we can build the return value\r\n // Uses `as any` to access private method — required by the intercept-and-capture pattern\r\n const origHandleWrite = (agent as any).handleWriteSummary.bind(agent) as (\r\n a: WriteSummaryArgs,\r\n ) => Promise<string>\r\n ;(agent as any).handleWriteSummary = async (args: WriteSummaryArgs) => {\r\n lastWriteArgs = args\r\n return origHandleWrite(args)\r\n }\r\n\r\n try {\r\n await agent.run(userPrompt)\r\n\r\n if (!lastWriteArgs) {\r\n throw new Error('SummaryAgent did not call write_summary')\r\n }\r\n\r\n return agent.getResult(lastWriteArgs)\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n","/**\r\n * CopilotProvider — wraps @github/copilot-sdk behind the LLMProvider interface.\r\n *\r\n * Extracts the Copilot-specific logic from BaseAgent into a reusable provider\r\n * that can be swapped with OpenAI or Claude providers via the abstraction layer.\r\n */\r\n\r\nimport { CopilotClient, CopilotSession, SessionEvent } from '@github/copilot-sdk'\r\nimport logger from '../config/logger.js'\r\nimport type {\r\n LLMProvider,\r\n LLMSession,\r\n LLMResponse,\r\n SessionConfig,\r\n TokenUsage,\r\n CostInfo,\r\n QuotaSnapshot,\r\n ToolCall,\r\n ProviderEvent,\r\n ProviderEventType,\r\n} from './types'\r\n\r\nconst DEFAULT_MODEL = 'Claude Opus 4.6'\r\nconst DEFAULT_TIMEOUT_MS = 300_000 // 5 minutes\r\n\r\nexport class CopilotProvider implements LLMProvider {\r\n readonly name = 'copilot' as const\r\n private client: CopilotClient | null = null\r\n\r\n isAvailable(): boolean {\r\n // Copilot uses GitHub auth, not an API key\r\n return true\r\n }\r\n\r\n getDefaultModel(): string {\r\n return DEFAULT_MODEL\r\n }\r\n\r\n async createSession(config: SessionConfig): Promise<LLMSession> {\r\n if (!this.client) {\r\n this.client = new CopilotClient({ autoStart: true, logLevel: 'error' })\r\n }\r\n\r\n const copilotSession = await this.client.createSession({\r\n systemMessage: { mode: 'replace', content: config.systemPrompt },\r\n tools: config.tools.map((t) => ({\r\n name: t.name,\r\n description: t.description,\r\n parameters: t.parameters,\r\n handler: t.handler,\r\n })),\r\n streaming: config.streaming ?? true,\r\n })\r\n\r\n return new CopilotSessionWrapper(\r\n copilotSession,\r\n config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\r\n )\r\n }\r\n\r\n /** Tear down the underlying Copilot client. */\r\n async close(): Promise<void> {\r\n try {\r\n if (this.client) {\r\n await this.client.stop()\r\n this.client = null\r\n }\r\n } catch (err) {\r\n logger.error(`[CopilotProvider] Error during close: ${err}`)\r\n }\r\n }\r\n}\r\n\r\n/** Wraps a CopilotSession to satisfy the LLMSession interface. */\r\nclass CopilotSessionWrapper implements LLMSession {\r\n private eventHandlers = new Map<ProviderEventType, Array<(event: ProviderEvent) => void>>()\r\n\r\n // Latest usage data captured from assistant.usage events\r\n private lastUsage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }\r\n private lastCost: CostInfo | undefined\r\n private lastQuotaSnapshots: Record<string, QuotaSnapshot> | undefined\r\n\r\n constructor(\r\n private readonly session: CopilotSession,\r\n private readonly timeoutMs: number,\r\n ) {\r\n this.setupEventForwarding()\r\n this.setupUsageTracking()\r\n }\r\n\r\n async sendAndWait(message: string): Promise<LLMResponse> {\r\n const start = Date.now()\r\n\r\n // Reset usage tracking for this call\r\n this.lastUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }\r\n this.lastCost = undefined\r\n this.lastQuotaSnapshots = undefined\r\n\r\n const response = await this.session.sendAndWait(\r\n { prompt: message },\r\n this.timeoutMs,\r\n )\r\n\r\n const content = response?.data?.content ?? ''\r\n const toolCalls: ToolCall[] = [] // Copilot SDK handles tool calls internally\r\n\r\n return {\r\n content,\r\n toolCalls,\r\n usage: this.lastUsage,\r\n cost: this.lastCost,\r\n quotaSnapshots: this.lastQuotaSnapshots,\r\n durationMs: Date.now() - start,\r\n }\r\n }\r\n\r\n on(event: ProviderEventType, handler: (event: ProviderEvent) => void): void {\r\n const handlers = this.eventHandlers.get(event) ?? []\r\n handlers.push(handler)\r\n this.eventHandlers.set(event, handlers)\r\n }\r\n\r\n async close(): Promise<void> {\r\n await this.session.destroy()\r\n this.eventHandlers.clear()\r\n }\r\n\r\n /** Capture assistant.usage events for token/cost tracking. */\r\n private setupUsageTracking(): void {\r\n this.session.on((event: SessionEvent) => {\r\n if (event.type === 'assistant.usage') {\r\n const d = event.data as Record<string, unknown>\r\n this.lastUsage = {\r\n inputTokens: (d.inputTokens as number) ?? 0,\r\n outputTokens: (d.outputTokens as number) ?? 0,\r\n totalTokens: ((d.inputTokens as number) ?? 0) + ((d.outputTokens as number) ?? 0),\r\n cacheReadTokens: d.cacheReadTokens as number | undefined,\r\n cacheWriteTokens: d.cacheWriteTokens as number | undefined,\r\n }\r\n if (d.cost != null) {\r\n this.lastCost = {\r\n amount: d.cost as number,\r\n unit: 'premium_requests',\r\n model: (d.model as string) ?? DEFAULT_MODEL,\r\n multiplier: d.multiplier as number | undefined,\r\n }\r\n }\r\n if (d.quotaSnapshots != null) {\r\n this.lastQuotaSnapshots = d.quotaSnapshots as Record<string, QuotaSnapshot>\r\n }\r\n }\r\n })\r\n }\r\n\r\n /** Forward CopilotSession events to ProviderEvent subscribers. */\r\n private setupEventForwarding(): void {\r\n this.session.on((event: SessionEvent) => {\r\n switch (event.type) {\r\n case 'assistant.message_delta':\r\n this.emit('delta', event.data)\r\n break\r\n case 'tool.execution_start':\r\n this.emit('tool_start', event.data)\r\n break\r\n case 'tool.execution_complete':\r\n this.emit('tool_end', event.data)\r\n break\r\n case 'assistant.usage':\r\n this.emit('usage', event.data)\r\n break\r\n case 'session.error':\r\n this.emit('error', event.data)\r\n break\r\n }\r\n })\r\n }\r\n\r\n private emit(type: ProviderEventType, data: unknown): void {\r\n const handlers = this.eventHandlers.get(type)\r\n if (handlers) {\r\n for (const handler of handlers) {\r\n handler({ type, data })\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * OpenAI Provider — wraps the OpenAI SDK behind the LLMProvider interface.\r\n *\r\n * Implements chat completions with automatic tool-calling loop:\r\n * user message → LLM → (tool_calls? → execute → feed back → LLM)* → final text\r\n */\r\n\r\nimport OpenAI from 'openai';\r\nimport type {\r\n ChatCompletionMessageParam,\r\n ChatCompletionTool,\r\n} from 'openai/resources/chat/completions.js';\r\nimport type {\r\n LLMProvider,\r\n LLMSession,\r\n LLMResponse,\r\n SessionConfig,\r\n ToolWithHandler,\r\n TokenUsage,\r\n ProviderEventType,\r\n ProviderEvent,\r\n} from './types.js';\r\nimport { calculateTokenCost } from '../config/pricing.js';\r\nimport logger from '../config/logger.js';\r\n\r\nconst MAX_TOOL_ROUNDS = 50;\r\n\r\n// ── helpers ────────────────────────────────────────────────────────────\r\n\r\n/** Convert our ToolWithHandler[] to the OpenAI SDK tool format. */\r\nfunction toOpenAITools(tools: ToolWithHandler[]): ChatCompletionTool[] {\r\n return tools.map((t) => ({\r\n type: 'function' as const,\r\n function: {\r\n name: t.name,\r\n description: t.description,\r\n parameters: t.parameters,\r\n },\r\n }));\r\n}\r\n\r\n/** Build a handler lookup map keyed by tool name. */\r\nfunction buildHandlerMap(\r\n tools: ToolWithHandler[],\r\n): Map<string, ToolWithHandler['handler']> {\r\n return new Map(tools.map((t) => [t.name, t.handler]));\r\n}\r\n\r\n/** Sum two TokenUsage objects. */\r\nfunction addUsage(a: TokenUsage, b: TokenUsage): TokenUsage {\r\n return {\r\n inputTokens: a.inputTokens + b.inputTokens,\r\n outputTokens: a.outputTokens + b.outputTokens,\r\n totalTokens: a.totalTokens + b.totalTokens,\r\n };\r\n}\r\n\r\n// ── session ────────────────────────────────────────────────────────────\r\n\r\nclass OpenAISession implements LLMSession {\r\n private client: OpenAI;\r\n private model: string;\r\n private messages: ChatCompletionMessageParam[];\r\n private tools: ChatCompletionTool[];\r\n private handlers: Map<string, ToolWithHandler['handler']>;\r\n private listeners = new Map<ProviderEventType, ((e: ProviderEvent) => void)[]>();\r\n private timeoutMs?: number;\r\n\r\n constructor(client: OpenAI, config: SessionConfig, model: string) {\r\n this.client = client;\r\n this.model = model;\r\n this.messages = [{ role: 'system', content: config.systemPrompt }];\r\n this.tools = toOpenAITools(config.tools);\r\n this.handlers = buildHandlerMap(config.tools);\r\n this.timeoutMs = config.timeoutMs;\r\n }\r\n\r\n // ── public API ─────────────────────────────────────────────────────\r\n\r\n async sendAndWait(message: string): Promise<LLMResponse> {\r\n this.messages.push({ role: 'user', content: message });\r\n\r\n let cumulative: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\r\n const start = Date.now();\r\n\r\n // Agent loop: keep calling the LLM until no tool_calls remain\r\n let toolRound = 0;\r\n while (true) {\r\n if (++toolRound > MAX_TOOL_ROUNDS) {\r\n logger.warn(`OpenAI agent exceeded ${MAX_TOOL_ROUNDS} tool rounds — aborting to prevent runaway`);\r\n throw new Error(`Max tool rounds (${MAX_TOOL_ROUNDS}) exceeded — possible infinite loop`);\r\n }\r\n const controller = new AbortController();\r\n const timeoutId = this.timeoutMs\r\n ? setTimeout(() => controller.abort(), this.timeoutMs)\r\n : undefined;\r\n let response: OpenAI.Chat.Completions.ChatCompletion;\r\n try {\r\n response = await this.client.chat.completions.create(\r\n {\r\n model: this.model,\r\n messages: this.messages,\r\n ...(this.tools.length > 0 ? { tools: this.tools } : {}),\r\n },\r\n { signal: controller.signal },\r\n );\r\n } finally {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n }\r\n\r\n const choice = response.choices[0];\r\n const assistantMsg = choice.message;\r\n\r\n // Accumulate token usage\r\n if (response.usage) {\r\n const iterUsage: TokenUsage = {\r\n inputTokens: response.usage.prompt_tokens,\r\n outputTokens: response.usage.completion_tokens,\r\n totalTokens: response.usage.total_tokens,\r\n };\r\n cumulative = addUsage(cumulative, iterUsage);\r\n this.emit('usage', iterUsage);\r\n }\r\n\r\n // Add assistant message to history\r\n this.messages.push(assistantMsg as ChatCompletionMessageParam);\r\n\r\n const toolCalls = assistantMsg.tool_calls;\r\n if (!toolCalls || toolCalls.length === 0) {\r\n // No more tool calls — return final response\r\n const cost = calculateTokenCost(this.model, cumulative.inputTokens, cumulative.outputTokens);\r\n return {\r\n content: assistantMsg.content ?? '',\r\n toolCalls: [],\r\n usage: cumulative,\r\n cost: { amount: cost, unit: 'usd', model: this.model },\r\n durationMs: Date.now() - start,\r\n };\r\n }\r\n\r\n // Execute each tool call and feed results back\r\n for (const tc of toolCalls) {\r\n if (tc.type !== 'function') continue;\r\n\r\n const fnName = tc.function.name;\r\n const handler = this.handlers.get(fnName);\r\n\r\n let result: unknown;\r\n if (!handler) {\r\n logger.warn(`OpenAI requested unknown tool: ${fnName}`);\r\n result = { error: `Unknown tool: ${fnName}` };\r\n } else {\r\n this.emit('tool_start', { name: fnName, arguments: tc.function.arguments });\r\n try {\r\n const args = JSON.parse(tc.function.arguments) as Record<string, unknown>;\r\n result = await handler(args);\r\n } catch (err) {\r\n logger.error(`Tool ${fnName} failed: ${err}`);\r\n result = { error: String(err) };\r\n }\r\n this.emit('tool_end', { name: fnName, result });\r\n }\r\n\r\n this.messages.push({\r\n role: 'tool',\r\n tool_call_id: tc.id,\r\n content: typeof result === 'string' ? result : JSON.stringify(result),\r\n });\r\n }\r\n // Loop back to call the LLM again with tool results\r\n }\r\n }\r\n\r\n on(event: ProviderEventType, handler: (e: ProviderEvent) => void): void {\r\n const list = this.listeners.get(event) ?? [];\r\n list.push(handler);\r\n this.listeners.set(event, list);\r\n }\r\n\r\n async close(): Promise<void> {\r\n this.messages = [];\r\n this.listeners.clear();\r\n }\r\n\r\n // ── internals ──────────────────────────────────────────────────────\r\n\r\n private emit(type: ProviderEventType, data: unknown): void {\r\n for (const handler of this.listeners.get(type) ?? []) {\r\n try {\r\n handler({ type, data });\r\n } catch {\r\n // Don't let listener errors break the agent loop\r\n }\r\n }\r\n }\r\n}\r\n\r\n// ── provider ───────────────────────────────────────────────────────────\r\n\r\nexport class OpenAIProvider implements LLMProvider {\r\n readonly name = 'openai' as const;\r\n\r\n isAvailable(): boolean {\r\n return !!process.env.OPENAI_API_KEY;\r\n }\r\n\r\n getDefaultModel(): string {\r\n return 'gpt-4o';\r\n }\r\n\r\n async createSession(config: SessionConfig): Promise<LLMSession> {\r\n const client = new OpenAI(); // reads OPENAI_API_KEY from env\r\n const model = config.model ?? this.getDefaultModel();\r\n logger.info(`OpenAI session created (model=${model}, tools=${config.tools.length})`);\r\n return new OpenAISession(client, config, model);\r\n }\r\n}\r\n","/**\r\n * LLM Model Pricing Configuration\r\n * \r\n * Per-model pricing for cost calculation. Updated Feb 2026.\r\n * Copilot uses Premium Request Units (PRUs), others use per-token pricing.\r\n */\r\n\r\nexport interface ModelPricing {\r\n /** Price per 1M input tokens (USD) — for OpenAI/Claude */\r\n inputPer1M?: number;\r\n /** Price per 1M output tokens (USD) — for OpenAI/Claude */\r\n outputPer1M?: number;\r\n /** Premium request multiplier — for Copilot */\r\n pruMultiplier?: number;\r\n /** Whether this model is included free on paid Copilot plans */\r\n copilotIncluded?: boolean;\r\n}\r\n\r\n/** Overage rate for Copilot premium requests: $0.04 per PRU */\r\nexport const COPILOT_PRU_OVERAGE_RATE = 0.04;\r\n\r\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\r\n // === OpenAI Models (from Copilot model picker) ===\r\n 'gpt-4o': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 0, copilotIncluded: true },\r\n 'gpt-4o-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0, copilotIncluded: true },\r\n 'gpt-4.1': { inputPer1M: 2.00, outputPer1M: 8.00, pruMultiplier: 0, copilotIncluded: true },\r\n 'gpt-4.1-mini': { inputPer1M: 0.40, outputPer1M: 1.60 },\r\n 'gpt-5-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0, copilotIncluded: true },\r\n 'gpt-5': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'gpt-5-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'gpt-5.1': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'gpt-5.1-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'gpt-5.1-codex-max': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'gpt-5.1-codex-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0.33 },\r\n 'gpt-5.2': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'gpt-5.2-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\r\n 'o3': { inputPer1M: 10.00, outputPer1M: 40.00, pruMultiplier: 5 },\r\n 'o4-mini-high': { inputPer1M: 1.10, outputPer1M: 4.40, pruMultiplier: 20 },\r\n\r\n // === Anthropic Models (from Copilot model picker) ===\r\n 'claude-haiku-4.5': { inputPer1M: 0.80, outputPer1M: 4.00, pruMultiplier: 0.33 },\r\n 'claude-sonnet-4': { inputPer1M: 3.00, outputPer1M: 15.00, pruMultiplier: 1 },\r\n 'claude-sonnet-4.5': { inputPer1M: 3.00, outputPer1M: 15.00, pruMultiplier: 1 },\r\n 'claude-opus-4.5': { inputPer1M: 15.00, outputPer1M: 75.00, pruMultiplier: 3 },\r\n 'claude-opus-4.6': { inputPer1M: 5.00, outputPer1M: 25.00, pruMultiplier: 3 },\r\n 'claude-opus-4.6-fast': { inputPer1M: 5.00, outputPer1M: 25.00, pruMultiplier: 9 },\r\n\r\n // === Google Models (from Copilot model picker) ===\r\n 'gemini-2.5-pro': { inputPer1M: 1.25, outputPer1M: 5.00, pruMultiplier: 1 },\r\n 'gemini-3-flash': { inputPer1M: 0.10, outputPer1M: 0.40, pruMultiplier: 0.33 },\r\n 'gemini-3-pro': { inputPer1M: 1.25, outputPer1M: 5.00, pruMultiplier: 1 },\r\n};\r\n\r\n/**\r\n * Calculate cost for a single LLM call using per-token pricing.\r\n * Returns USD amount.\r\n */\r\nexport function calculateTokenCost(\r\n model: string,\r\n inputTokens: number,\r\n outputTokens: number\r\n): number {\r\n const pricing = getModelPricing(model);\r\n if (!pricing || (!pricing.inputPer1M && !pricing.outputPer1M)) return 0;\r\n \r\n const inputCost = ((pricing.inputPer1M ?? 0) / 1_000_000) * inputTokens;\r\n const outputCost = ((pricing.outputPer1M ?? 0) / 1_000_000) * outputTokens;\r\n return inputCost + outputCost;\r\n}\r\n\r\n/**\r\n * Calculate PRU cost for a Copilot premium request.\r\n * Returns PRU count consumed (multiply by $0.04 for overage cost).\r\n */\r\nexport function calculatePRUCost(model: string): number {\r\n const pricing = getModelPricing(model);\r\n if (!pricing) return 1; // Default 1 PRU for unknown models\r\n if (pricing.copilotIncluded) return 0; // Free on paid plans\r\n return pricing.pruMultiplier ?? 1;\r\n}\r\n\r\n/**\r\n * Look up model pricing. Returns undefined if model is unknown.\r\n */\r\nexport function getModelPricing(model: string): ModelPricing | undefined {\r\n // Try exact match first, then case-insensitive\r\n return MODEL_PRICING[model] ?? \r\n MODEL_PRICING[model.toLowerCase()] ??\r\n Object.entries(MODEL_PRICING).find(([key]) => \r\n model.toLowerCase().includes(key.toLowerCase())\r\n )?.[1];\r\n}\r\n","/**\r\n * Claude (Anthropic) LLM Provider\r\n *\r\n * Wraps the Anthropic Messages API behind the LLMProvider interface.\r\n * Uses direct @anthropic-ai/sdk for tool-calling with our own agent loop.\r\n */\r\n\r\nimport Anthropic from '@anthropic-ai/sdk'\r\nimport type {\r\n ContentBlock,\r\n MessageParam,\r\n TextBlock,\r\n Tool,\r\n ToolResultBlockParam,\r\n ToolUseBlock,\r\n} from '@anthropic-ai/sdk/resources/messages.js'\r\nimport { calculateTokenCost } from '../config/pricing.js'\r\nimport logger from '../config/logger.js'\r\nimport type {\r\n LLMProvider,\r\n LLMSession,\r\n LLMResponse,\r\n SessionConfig,\r\n ToolWithHandler,\r\n TokenUsage,\r\n ProviderEventType,\r\n ProviderEvent,\r\n} from './types.js'\r\n\r\nconst DEFAULT_MODEL = 'claude-opus-4.6'\r\nconst DEFAULT_MAX_TOKENS = 8192\r\nconst MAX_TOOL_ROUNDS = 50\r\n\r\n/** Convert our ToolWithHandler[] to Anthropic tool format */\r\nfunction toAnthropicTools(tools: ToolWithHandler[]): Tool[] {\r\n return tools.map((t) => ({\r\n name: t.name,\r\n description: t.description,\r\n input_schema: t.parameters as Tool['input_schema'],\r\n }))\r\n}\r\n\r\n/** Extract text content from Anthropic response content blocks */\r\nfunction extractText(content: ContentBlock[]): string {\r\n return content\r\n .filter((b): b is TextBlock => b.type === 'text')\r\n .map((b) => b.text)\r\n .join('')\r\n}\r\n\r\n/** Extract tool_use blocks from Anthropic response */\r\nfunction extractToolUse(content: ContentBlock[]): ToolUseBlock[] {\r\n return content.filter((b): b is ToolUseBlock => b.type === 'tool_use')\r\n}\r\n\r\nclass ClaudeSession implements LLMSession {\r\n private client: Anthropic\r\n private systemPrompt: string\r\n private tools: ToolWithHandler[]\r\n private anthropicTools: Tool[]\r\n private messages: MessageParam[] = []\r\n private model: string\r\n private maxTokens: number\r\n private handlers = new Map<ProviderEventType, ((event: ProviderEvent) => void)[]>()\r\n private timeoutMs?: number\r\n\r\n constructor(client: Anthropic, config: SessionConfig) {\r\n this.client = client\r\n this.systemPrompt = config.systemPrompt\r\n this.tools = config.tools\r\n this.anthropicTools = toAnthropicTools(config.tools)\r\n this.model = config.model ?? DEFAULT_MODEL\r\n this.maxTokens = DEFAULT_MAX_TOKENS\r\n this.timeoutMs = config.timeoutMs\r\n }\r\n\r\n on(event: ProviderEventType, handler: (event: ProviderEvent) => void): void {\r\n const list = this.handlers.get(event) ?? []\r\n list.push(handler)\r\n this.handlers.set(event, list)\r\n }\r\n\r\n private emit(type: ProviderEventType, data: unknown): void {\r\n for (const handler of this.handlers.get(type) ?? []) {\r\n handler({ type, data })\r\n }\r\n }\r\n\r\n async sendAndWait(message: string): Promise<LLMResponse> {\r\n this.messages.push({ role: 'user', content: message })\r\n\r\n let cumulativeUsage: TokenUsage = {\r\n inputTokens: 0,\r\n outputTokens: 0,\r\n totalTokens: 0,\r\n }\r\n\r\n const startMs = Date.now()\r\n\r\n // Agent loop: keep calling until no more tool_use\r\n let toolRound = 0\r\n while (true) {\r\n if (++toolRound > MAX_TOOL_ROUNDS) {\r\n logger.warn(`Claude agent exceeded ${MAX_TOOL_ROUNDS} tool rounds — aborting to prevent runaway`)\r\n throw new Error(`Max tool rounds (${MAX_TOOL_ROUNDS}) exceeded — possible infinite loop`)\r\n }\r\n const controller = new AbortController()\r\n const timeoutId = this.timeoutMs\r\n ? setTimeout(() => controller.abort(), this.timeoutMs)\r\n : undefined\r\n let response: Anthropic.Messages.Message\r\n try {\r\n response = await this.client.messages.create(\r\n {\r\n model: this.model,\r\n max_tokens: this.maxTokens,\r\n system: this.systemPrompt,\r\n messages: this.messages,\r\n ...(this.anthropicTools.length > 0 ? { tools: this.anthropicTools } : {}),\r\n },\r\n { signal: controller.signal },\r\n )\r\n } finally {\r\n if (timeoutId) clearTimeout(timeoutId)\r\n }\r\n\r\n // Accumulate usage\r\n cumulativeUsage.inputTokens += response.usage.input_tokens\r\n cumulativeUsage.outputTokens += response.usage.output_tokens\r\n cumulativeUsage.totalTokens =\r\n cumulativeUsage.inputTokens + cumulativeUsage.outputTokens\r\n\r\n if (response.usage.cache_read_input_tokens) {\r\n cumulativeUsage.cacheReadTokens =\r\n (cumulativeUsage.cacheReadTokens ?? 0) + response.usage.cache_read_input_tokens\r\n }\r\n if (response.usage.cache_creation_input_tokens) {\r\n cumulativeUsage.cacheWriteTokens =\r\n (cumulativeUsage.cacheWriteTokens ?? 0) + response.usage.cache_creation_input_tokens\r\n }\r\n\r\n this.emit('usage', cumulativeUsage)\r\n\r\n // Add assistant response to history\r\n this.messages.push({ role: 'assistant', content: response.content })\r\n\r\n const toolUseBlocks = extractToolUse(response.content)\r\n\r\n if (toolUseBlocks.length === 0 || response.stop_reason === 'end_turn') {\r\n // No tool calls — return final text\r\n const text = extractText(response.content)\r\n const cost = calculateTokenCost(\r\n this.model,\r\n cumulativeUsage.inputTokens,\r\n cumulativeUsage.outputTokens,\r\n )\r\n\r\n return {\r\n content: text,\r\n toolCalls: [],\r\n usage: cumulativeUsage,\r\n cost: cost > 0\r\n ? { amount: cost, unit: 'usd', model: this.model }\r\n : undefined,\r\n durationMs: Date.now() - startMs,\r\n }\r\n }\r\n\r\n // Execute tool calls and build result messages\r\n const toolResults: ToolResultBlockParam[] = []\r\n\r\n for (const block of toolUseBlocks) {\r\n const tool = this.tools.find((t) => t.name === block.name)\r\n if (!tool) {\r\n logger.warn(`Claude requested unknown tool: ${block.name}`)\r\n toolResults.push({\r\n type: 'tool_result',\r\n tool_use_id: block.id,\r\n content: JSON.stringify({ error: `Unknown tool: ${block.name}` }),\r\n })\r\n continue\r\n }\r\n\r\n this.emit('tool_start', { name: block.name, arguments: block.input })\r\n\r\n try {\r\n const result = await tool.handler(block.input as Record<string, unknown>)\r\n toolResults.push({\r\n type: 'tool_result',\r\n tool_use_id: block.id,\r\n content: JSON.stringify(result),\r\n })\r\n this.emit('tool_end', { name: block.name, result })\r\n } catch (err) {\r\n const errorMsg = err instanceof Error ? err.message : String(err)\r\n logger.error(`Tool ${block.name} failed: ${errorMsg}`)\r\n toolResults.push({\r\n type: 'tool_result',\r\n tool_use_id: block.id,\r\n content: JSON.stringify({ error: errorMsg }),\r\n is_error: true,\r\n })\r\n this.emit('error', { name: block.name, error: errorMsg })\r\n }\r\n }\r\n\r\n // Add tool results as a user message and loop\r\n this.messages.push({ role: 'user', content: toolResults })\r\n }\r\n }\r\n\r\n async close(): Promise<void> {\r\n this.messages = []\r\n this.handlers.clear()\r\n }\r\n}\r\n\r\nexport class ClaudeProvider implements LLMProvider {\r\n readonly name = 'claude' as const\r\n\r\n isAvailable(): boolean {\r\n return !!process.env.ANTHROPIC_API_KEY\r\n }\r\n\r\n getDefaultModel(): string {\r\n return DEFAULT_MODEL\r\n }\r\n\r\n async createSession(config: SessionConfig): Promise<LLMSession> {\r\n const client = new Anthropic()\r\n return new ClaudeSession(client, config)\r\n }\r\n}\r\n","import type { LLMProvider } from './types.js';\r\nimport type { ProviderName } from './types.js';\r\nimport { CopilotProvider } from './CopilotProvider.js';\r\nimport { OpenAIProvider } from './OpenAIProvider.js';\r\nimport { ClaudeProvider } from './ClaudeProvider.js';\r\nimport logger from '../config/logger.js';\r\n\r\nconst providers: Record<ProviderName, () => LLMProvider> = {\r\n copilot: () => new CopilotProvider(),\r\n openai: () => new OpenAIProvider(),\r\n claude: () => new ClaudeProvider(),\r\n};\r\n\r\n/** Cached singleton provider instance */\r\nlet currentProvider: LLMProvider | null = null;\r\nlet currentProviderName: ProviderName | null = null;\r\n\r\n/**\r\n * Get the configured LLM provider.\r\n * Reads from LLM_PROVIDER env var, defaults to 'copilot'.\r\n * Caches the instance for reuse.\r\n */\r\nexport function getProvider(name?: ProviderName): LLMProvider {\r\n const raw = name ?? (process.env.LLM_PROVIDER || 'copilot').trim().toLowerCase();\r\n const providerName = raw as ProviderName;\r\n \r\n if (currentProvider && currentProviderName === providerName) {\r\n return currentProvider;\r\n }\r\n\r\n // Close old provider if switching to a different one\r\n currentProvider?.close?.().catch(() => { /* ignore close errors */ });\r\n\r\n if (!providers[providerName]) {\r\n throw new Error(\r\n `Unknown LLM provider: \"${providerName}\". ` +\r\n `Valid options: ${Object.keys(providers).join(', ')}`\r\n );\r\n }\r\n\r\n const provider = providers[providerName]();\r\n \r\n if (!provider.isAvailable()) {\r\n logger.warn(\r\n `Provider \"${providerName}\" is not available (missing API key or config). ` +\r\n `Falling back to copilot provider.`\r\n );\r\n currentProvider = providers.copilot();\r\n currentProviderName = 'copilot';\r\n return currentProvider;\r\n }\r\n\r\n logger.info(`Using LLM provider: ${providerName} (model: ${provider.getDefaultModel()})`);\r\n currentProvider = provider;\r\n currentProviderName = providerName;\r\n return currentProvider;\r\n}\r\n\r\n/** Reset the cached provider (for testing) */\r\nexport async function resetProvider(): Promise<void> {\r\n try { await currentProvider?.close?.(); } catch { /* ignore close errors */ }\r\n currentProvider = null;\r\n currentProviderName = null;\r\n}\r\n\r\n/** Get the name of the current provider */\r\nexport function getProviderName(): ProviderName {\r\n const raw = (process.env.LLM_PROVIDER || 'copilot').trim().toLowerCase();\r\n const valid: ProviderName[] = ['copilot', 'openai', 'claude'];\r\n return currentProviderName ?? (valid.includes(raw as ProviderName) ? (raw as ProviderName) : 'copilot');\r\n}\r\n\r\n// Re-export types and providers\r\nexport type { LLMProvider, LLMSession, LLMResponse, SessionConfig, ToolWithHandler, TokenUsage, CostInfo, QuotaSnapshot, ProviderEvent, ProviderEventType } from './types.js';\r\nexport type { ProviderName } from './types.js';\r\nexport { CopilotProvider } from './CopilotProvider.js';\r\nexport { OpenAIProvider } from './OpenAIProvider.js';\r\nexport { ClaudeProvider } from './ClaudeProvider.js';\r\n","import type { TokenUsage, CostInfo, QuotaSnapshot } from '../providers/types.js';\r\nimport { calculateTokenCost, calculatePRUCost, COPILOT_PRU_OVERAGE_RATE } from '../config/pricing.js';\r\nimport logger from '../config/logger.js';\r\n\r\n/** Record of a single LLM usage event */\r\nexport interface UsageRecord {\r\n timestamp: Date;\r\n provider: string;\r\n model: string;\r\n agent: string;\r\n stage: string;\r\n usage: TokenUsage;\r\n cost: CostInfo;\r\n durationMs?: number;\r\n}\r\n\r\n/** Aggregated cost report */\r\nexport interface CostReport {\r\n totalCostUSD: number;\r\n totalPRUs: number;\r\n totalTokens: { input: number; output: number; total: number };\r\n byProvider: Record<string, { costUSD: number; prus: number; calls: number }>;\r\n byAgent: Record<string, { costUSD: number; prus: number; calls: number }>;\r\n byModel: Record<string, { costUSD: number; prus: number; calls: number }>;\r\n records: UsageRecord[];\r\n /** Copilot quota info (if available) */\r\n copilotQuota?: QuotaSnapshot;\r\n}\r\n\r\n/** Singleton cost tracker for a pipeline run */\r\nclass CostTracker {\r\n private records: UsageRecord[] = [];\r\n private latestQuota?: QuotaSnapshot;\r\n private currentAgent = 'unknown';\r\n private currentStage = 'unknown';\r\n\r\n /** Set the current agent name (called by BaseAgent before LLM calls) */\r\n setAgent(agent: string): void {\r\n this.currentAgent = agent;\r\n }\r\n\r\n /** Set the current pipeline stage */\r\n setStage(stage: string): void {\r\n this.currentStage = stage;\r\n }\r\n\r\n /** Record a usage event from any provider */\r\n recordUsage(\r\n provider: string,\r\n model: string,\r\n usage: TokenUsage,\r\n cost?: CostInfo,\r\n durationMs?: number,\r\n quotaSnapshot?: QuotaSnapshot\r\n ): void {\r\n // Calculate cost if not provided\r\n const finalCost = cost ?? {\r\n amount: provider === 'copilot'\r\n ? calculatePRUCost(model)\r\n : calculateTokenCost(model, usage.inputTokens, usage.outputTokens),\r\n unit: provider === 'copilot' ? 'premium_requests' as const : 'usd' as const,\r\n model,\r\n };\r\n\r\n const record: UsageRecord = {\r\n timestamp: new Date(),\r\n provider,\r\n model,\r\n agent: this.currentAgent,\r\n stage: this.currentStage,\r\n usage,\r\n cost: finalCost,\r\n durationMs,\r\n };\r\n\r\n this.records.push(record);\r\n\r\n if (quotaSnapshot) {\r\n this.latestQuota = quotaSnapshot;\r\n }\r\n\r\n logger.debug(\r\n `[CostTracker] ${provider}/${model} | ${this.currentAgent} | ` +\r\n `in=${usage.inputTokens} out=${usage.outputTokens} | ` +\r\n `cost=${finalCost.amount.toFixed(4)} ${finalCost.unit}`\r\n );\r\n }\r\n\r\n /** Get the full cost report */\r\n getReport(): CostReport {\r\n const report: CostReport = {\r\n totalCostUSD: 0,\r\n totalPRUs: 0,\r\n totalTokens: { input: 0, output: 0, total: 0 },\r\n byProvider: {},\r\n byAgent: {},\r\n byModel: {},\r\n records: [...this.records],\r\n copilotQuota: this.latestQuota,\r\n };\r\n\r\n for (const record of this.records) {\r\n const { provider, model, agent, usage, cost } = record;\r\n\r\n // Accumulate tokens\r\n report.totalTokens.input += usage.inputTokens;\r\n report.totalTokens.output += usage.outputTokens;\r\n report.totalTokens.total += usage.totalTokens;\r\n\r\n // Accumulate costs\r\n const usdCost = cost.unit === 'usd' ? cost.amount : cost.amount * COPILOT_PRU_OVERAGE_RATE;\r\n const prus = cost.unit === 'premium_requests' ? cost.amount : 0;\r\n report.totalCostUSD += usdCost;\r\n report.totalPRUs += prus;\r\n\r\n // By provider\r\n if (!report.byProvider[provider]) report.byProvider[provider] = { costUSD: 0, prus: 0, calls: 0 };\r\n report.byProvider[provider].costUSD += usdCost;\r\n report.byProvider[provider].prus += prus;\r\n report.byProvider[provider].calls += 1;\r\n\r\n // By agent\r\n if (!report.byAgent[agent]) report.byAgent[agent] = { costUSD: 0, prus: 0, calls: 0 };\r\n report.byAgent[agent].costUSD += usdCost;\r\n report.byAgent[agent].prus += prus;\r\n report.byAgent[agent].calls += 1;\r\n\r\n // By model\r\n if (!report.byModel[model]) report.byModel[model] = { costUSD: 0, prus: 0, calls: 0 };\r\n report.byModel[model].costUSD += usdCost;\r\n report.byModel[model].prus += prus;\r\n report.byModel[model].calls += 1;\r\n }\r\n\r\n return report;\r\n }\r\n\r\n /** Format report as human-readable string for console output */\r\n formatReport(): string {\r\n const report = this.getReport();\r\n const lines: string[] = [\r\n '',\r\n '═══════════════════════════════════════════',\r\n ' 💰 Pipeline Cost Report',\r\n '═══════════════════════════════════════════',\r\n '',\r\n ` Total Cost: $${report.totalCostUSD.toFixed(4)} USD`,\r\n ];\r\n\r\n if (report.totalPRUs > 0) {\r\n lines.push(` Total PRUs: ${report.totalPRUs} premium requests`);\r\n }\r\n\r\n lines.push(\r\n ` Total Tokens: ${report.totalTokens.total.toLocaleString()} (${report.totalTokens.input.toLocaleString()} in / ${report.totalTokens.output.toLocaleString()} out)`,\r\n ` LLM Calls: ${this.records.length}`,\r\n );\r\n\r\n if (report.copilotQuota) {\r\n lines.push(\r\n '',\r\n ` Copilot Quota: ${report.copilotQuota.remainingPercentage.toFixed(1)}% remaining`,\r\n ` Used/Total: ${report.copilotQuota.usedRequests}/${report.copilotQuota.entitlementRequests} PRUs`,\r\n );\r\n if (report.copilotQuota.resetDate) {\r\n lines.push(` Resets: ${report.copilotQuota.resetDate}`);\r\n }\r\n }\r\n\r\n // By agent breakdown\r\n if (Object.keys(report.byAgent).length > 1) {\r\n lines.push('', ' By Agent:');\r\n for (const [agent, data] of Object.entries(report.byAgent)) {\r\n lines.push(` ${agent}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\r\n }\r\n }\r\n\r\n // By model breakdown\r\n if (Object.keys(report.byModel).length > 1) {\r\n lines.push('', ' By Model:');\r\n for (const [model, data] of Object.entries(report.byModel)) {\r\n lines.push(` ${model}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\r\n }\r\n }\r\n\r\n lines.push('', '═══════════════════════════════════════════', '');\r\n return lines.join('\\n');\r\n }\r\n\r\n /** Reset all tracking (for new pipeline run) */\r\n reset(): void {\r\n this.records = [];\r\n this.latestQuota = undefined;\r\n this.currentAgent = 'unknown';\r\n this.currentStage = 'unknown';\r\n }\r\n}\r\n\r\n/** Global singleton instance */\r\nexport const costTracker = new CostTracker();\r\n","import type { LLMProvider, LLMSession, ToolWithHandler } from '../providers/types.js'\r\nimport { getProvider } from '../providers/index.js'\r\nimport { costTracker } from '../services/costTracker.js'\r\nimport logger from '../config/logger.js'\r\n\r\n/**\r\n * BaseAgent — abstract foundation for all LLM-powered agents.\r\n *\r\n * ### Agent pattern\r\n * Each agent in the pipeline (SummaryAgent, ShortsAgent, BlogAgent, etc.)\r\n * extends BaseAgent and implements two methods:\r\n * - `getTools()` — declares the tools (functions) the LLM can call\r\n * - `handleToolCall()` — dispatches tool invocations to concrete implementations\r\n *\r\n * ### Tool registration\r\n * Tools are declared as JSON Schema objects and passed to the LLMSession\r\n * at creation time. When the LLM decides to call a tool, the provider routes\r\n * the call through the tool handler where the subclass executes the actual\r\n * logic (e.g. reading files, running FFmpeg, querying APIs).\r\n *\r\n * ### Message flow\r\n * 1. `run(userMessage)` lazily creates an LLMSession via the configured\r\n * provider (Copilot, OpenAI, or Claude).\r\n * 2. The user message is sent via `sendAndWait()`, which blocks until the\r\n * LLM produces a final response (with a 5-minute timeout).\r\n * 3. During processing, the LLM may invoke tools multiple times — each call\r\n * is logged via session event handlers.\r\n * 4. The final assistant message text is returned to the caller.\r\n *\r\n * Sessions are reusable: calling `run()` multiple times on the same agent\r\n * sends additional messages within the same conversation context.\r\n */\r\nexport abstract class BaseAgent {\r\n protected provider: LLMProvider\r\n protected session: LLMSession | null = null\r\n\r\n constructor(\r\n protected readonly agentName: string,\r\n protected readonly systemPrompt: string,\r\n provider?: LLMProvider,\r\n ) {\r\n this.provider = provider ?? getProvider()\r\n }\r\n\r\n /** Tools this agent exposes to the LLM. Override in subclasses. */\r\n protected getTools(): ToolWithHandler[] {\r\n return []\r\n }\r\n\r\n /** Dispatch a tool call to the concrete agent. Override in subclasses. */\r\n protected abstract handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown>\r\n\r\n /**\r\n * Send a user message to the agent and return the final response text.\r\n *\r\n * 1. Lazily creates an LLMSession via the provider\r\n * 2. Registers event listeners for logging\r\n * 3. Calls sendAndWait and records usage via CostTracker\r\n */\r\n async run(userMessage: string): Promise<string> {\r\n if (!this.session) {\r\n this.session = await this.provider.createSession({\r\n systemPrompt: this.systemPrompt,\r\n tools: this.getTools(),\r\n streaming: true,\r\n model: process.env.LLM_MODEL || undefined,\r\n timeoutMs: 300_000, // 5 min timeout\r\n })\r\n this.setupEventHandlers(this.session)\r\n }\r\n\r\n logger.info(`[${this.agentName}] Sending message: ${userMessage.substring(0, 80)}…`)\r\n\r\n costTracker.setAgent(this.agentName)\r\n const response = await this.session.sendAndWait(userMessage)\r\n\r\n // Record usage via CostTracker\r\n costTracker.recordUsage(\r\n this.provider.name,\r\n response.cost?.model ?? this.provider.getDefaultModel(),\r\n response.usage,\r\n response.cost,\r\n response.durationMs,\r\n response.quotaSnapshots\r\n ? Object.values(response.quotaSnapshots)[0]\r\n : undefined,\r\n )\r\n\r\n const content = response.content\r\n logger.info(`[${this.agentName}] Response received (${content.length} chars)`)\r\n return content\r\n }\r\n\r\n /** Wire up session event listeners for logging. */\r\n private setupEventHandlers(session: LLMSession): void {\r\n session.on('delta', (event) => {\r\n logger.debug(`[${this.agentName}] delta: ${JSON.stringify(event.data)}`)\r\n })\r\n\r\n session.on('tool_start', (event) => {\r\n logger.info(`[${this.agentName}] tool start: ${JSON.stringify(event.data)}`)\r\n })\r\n\r\n session.on('tool_end', (event) => {\r\n logger.info(`[${this.agentName}] tool done: ${JSON.stringify(event.data)}`)\r\n })\r\n\r\n session.on('error', (event) => {\r\n logger.error(`[${this.agentName}] error: ${JSON.stringify(event.data)}`)\r\n })\r\n }\r\n\r\n /** Tear down the session. */\r\n async destroy(): Promise<void> {\r\n try {\r\n if (this.session) {\r\n await this.session.close()\r\n this.session = null\r\n }\r\n } catch (err) {\r\n logger.error(`[${this.agentName}] Error during destroy: ${err}`)\r\n }\r\n }\r\n}\r\n","import ffmpeg from 'fluent-ffmpeg';\r\nimport { promises as fs } from 'fs';\r\nimport path from 'path';\r\nimport logger from '../../config/logger';\r\nimport { getFFmpegPath, getFFprobePath } from '../../config/ffmpegResolver.js';\r\n\r\nconst ffmpegPath = getFFmpegPath();\r\nconst ffprobePath = getFFprobePath();\r\nffmpeg.setFfmpegPath(ffmpegPath);\r\nffmpeg.setFfprobePath(ffprobePath);\r\n\r\n/**\r\n * Extract a single PNG frame at the given timestamp (seconds).\r\n */\r\nexport async function captureFrame(\r\n videoPath: string,\r\n timestamp: number,\r\n outputPath: string,\r\n): Promise<string> {\r\n const outputDir = path.dirname(outputPath);\r\n await fs.mkdir(outputDir, { recursive: true });\r\n\r\n logger.info(`Capturing frame at ${timestamp}s → ${outputPath}`);\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n ffmpeg(videoPath)\r\n .seekInput(timestamp)\r\n .frames(1)\r\n .output(outputPath)\r\n .on('end', () => {\r\n logger.info(`Frame captured: ${outputPath}`);\r\n resolve(outputPath);\r\n })\r\n .on('error', (err) => {\r\n logger.error(`Frame capture failed: ${err.message}`);\r\n reject(new Error(`Frame capture failed: ${err.message}`));\r\n })\r\n .run();\r\n });\r\n}\r\n\r\n/**\r\n * Extract multiple frames at the given timestamps.\r\n * Files are named snapshot-001.png, snapshot-002.png, etc.\r\n */\r\nexport async function captureFrames(\r\n videoPath: string,\r\n timestamps: number[],\r\n outputDir: string,\r\n): Promise<string[]> {\r\n await fs.mkdir(outputDir, { recursive: true });\r\n\r\n const results: string[] = [];\r\n\r\n for (let i = 0; i < timestamps.length; i++) {\r\n const idx = String(i + 1).padStart(3, '0');\r\n const outputPath = path.join(outputDir, `snapshot-${idx}.png`);\r\n await captureFrame(videoPath, timestamps[i], outputPath);\r\n results.push(outputPath);\r\n }\r\n\r\n logger.info(`Captured ${results.length} frames in ${outputDir}`);\r\n return results;\r\n}\r\n","import ffmpeg from 'fluent-ffmpeg';\r\nimport { execFile } from 'child_process';\r\nimport { promises as fs } from 'fs';\r\nimport pathMod from 'path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport logger from '../../config/logger';\r\nimport { ShortSegment } from '../../types';\r\nimport { getFFmpegPath, getFFprobePath } from '../../config/ffmpegResolver.js';\r\n\r\nconst ffmpegPath = getFFmpegPath();\r\nconst ffprobePath = getFFprobePath();\r\nffmpeg.setFfmpegPath(ffmpegPath);\r\nffmpeg.setFfprobePath(ffprobePath);\r\n\r\nconst DEFAULT_FPS = 25;\r\n\r\n/**\r\n * Probe the source video's frame rate using ffprobe.\r\n * Returns a rounded integer fps, or DEFAULT_FPS if probing fails.\r\n * Needed because FFmpeg 7.x xfade requires constant-framerate inputs.\r\n */\r\nasync function getVideoFps(videoPath: string): Promise<number> {\r\n return new Promise<number>((resolve) => {\r\n execFile(\r\n ffprobePath,\r\n ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', '-of', 'csv=p=0', videoPath],\r\n { timeout: 5000 },\r\n (error, stdout) => {\r\n if (error || !stdout.trim()) {\r\n resolve(DEFAULT_FPS);\r\n return;\r\n }\r\n const parts = stdout.trim().split('/');\r\n const fps = parts.length === 2 ? parseInt(parts[0]) / parseInt(parts[1]) : parseFloat(stdout.trim());\r\n resolve(isFinite(fps) && fps > 0 ? Math.round(fps) : DEFAULT_FPS);\r\n },\r\n );\r\n });\r\n}\r\n\r\n/**\r\n * Extract a single clip segment using re-encode for frame-accurate timing.\r\n *\r\n * ### Why re-encode instead of `-c copy`?\r\n * Stream copy (`-c copy`) seeks to the nearest **keyframe** before the\r\n * requested start time, which creates a PTS offset between the clip's actual\r\n * start and the timestamp the caption generator assumes. This causes\r\n * captions to be out of sync with the audio — especially visible in\r\n * landscape-captioned shorts where there's no intermediate re-encode to\r\n * normalize PTS (the portrait path gets an extra re-encode via aspect-ratio\r\n * conversion which masks the issue).\r\n *\r\n * Re-encoding with `trim` + `setpts=PTS-STARTPTS` guarantees:\r\n * - The clip starts at **exactly** `bufferedStart` (not the nearest keyframe)\r\n * - Output PTS starts at 0 with no offset\r\n * - Caption timestamps align perfectly with both audio and video\r\n *\r\n * @param buffer Seconds of padding added before start and after end (default 1.0)\r\n */\r\nexport async function extractClip(\r\n videoPath: string,\r\n start: number,\r\n end: number,\r\n outputPath: string,\r\n buffer: number = 1.0,\r\n): Promise<string> {\r\n const outputDir = pathMod.dirname(outputPath);\r\n await fs.mkdir(outputDir, { recursive: true });\r\n\r\n const bufferedStart = Math.max(0, start - buffer);\r\n const bufferedEnd = end + buffer;\r\n const duration = bufferedEnd - bufferedStart;\r\n logger.info(`Extracting clip [${start}s–${end}s] (buffered: ${bufferedStart.toFixed(2)}s–${bufferedEnd.toFixed(2)}s) → ${outputPath}`);\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n ffmpeg(videoPath)\r\n .setStartTime(bufferedStart)\r\n .setDuration(duration)\r\n .outputOptions(['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '23', '-threads', '4', '-c:a', 'aac', '-b:a', '128k'])\r\n .output(outputPath)\r\n .on('end', () => {\r\n logger.info(`Clip extraction complete: ${outputPath}`);\r\n resolve(outputPath);\r\n })\r\n .on('error', (err) => {\r\n logger.error(`Clip extraction failed: ${err.message}`);\r\n reject(new Error(`Clip extraction failed: ${err.message}`));\r\n })\r\n .run();\r\n });\r\n}\r\n\r\n/**\r\n * Extract multiple non-contiguous segments and concatenate them into one clip.\r\n * Each segment is padded by `buffer` seconds on both sides for smoother cuts.\r\n * Re-encodes and uses concat demuxer for clean joins.\r\n * @param buffer Seconds of padding added before start and after end of each segment (default 1.0)\r\n */\r\nexport async function extractCompositeClip(\r\n videoPath: string,\r\n segments: ShortSegment[],\r\n outputPath: string,\r\n buffer: number = 1.0,\r\n): Promise<string> {\r\n if (!segments || segments.length === 0) {\r\n throw new Error('At least one segment is required');\r\n }\r\n\r\n if (segments.length === 1) {\r\n return extractClip(videoPath, segments[0].start, segments[0].end, outputPath, buffer);\r\n }\r\n\r\n const outputDir = pathMod.dirname(outputPath);\r\n await fs.mkdir(outputDir, { recursive: true });\r\n\r\n const tempDir = pathMod.join(outputDir, `.temp-${randomUUID()}`);\r\n await fs.mkdir(tempDir, { recursive: true });\r\n\r\n const tempFiles: string[] = [];\r\n\r\n try {\r\n // Extract each segment to a temp file (re-encode for reliable concat)\r\n for (let i = 0; i < segments.length; i++) {\r\n const seg = segments[i];\r\n const tempPath = pathMod.join(tempDir, `segment-${i}.mp4`);\r\n tempFiles.push(tempPath);\r\n\r\n const bufferedStart = Math.max(0, seg.start - buffer);\r\n const bufferedEnd = seg.end + buffer;\r\n logger.info(`Extracting segment ${i + 1}/${segments.length} [${seg.start}s–${seg.end}s] (buffered: ${bufferedStart.toFixed(2)}s–${bufferedEnd.toFixed(2)}s)`);\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n ffmpeg(videoPath)\r\n .setStartTime(bufferedStart)\r\n .setDuration(bufferedEnd - bufferedStart)\r\n .outputOptions(['-threads', '4', '-preset', 'ultrafast'])\r\n .output(tempPath)\r\n .on('end', () => resolve())\r\n .on('error', (err) => reject(new Error(`Segment ${i} extraction failed: ${err.message}`)))\r\n .run();\r\n });\r\n }\r\n\r\n // Build concat list file\r\n const concatListPath = pathMod.join(tempDir, 'concat-list.txt');\r\n const listContent = tempFiles.map((f) => `file '${f.replace(/'/g, \"'\\\\''\")}'`).join('\\n');\r\n await fs.writeFile(concatListPath, listContent);\r\n\r\n // Concatenate segments (re-encode for clean joins across buffered segments)\r\n logger.info(`Concatenating ${segments.length} segments → ${outputPath}`);\r\n await new Promise<void>((resolve, reject) => {\r\n ffmpeg()\r\n .input(concatListPath)\r\n .inputOptions(['-f', 'concat', '-safe', '0'])\r\n .outputOptions(['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '23', '-threads', '4', '-c:a', 'aac'])\r\n .output(outputPath)\r\n .on('end', () => resolve())\r\n .on('error', (err) => reject(new Error(`Concat failed: ${err.message}`)))\r\n .run();\r\n });\r\n\r\n logger.info(`Composite clip complete: ${outputPath}`);\r\n return outputPath;\r\n } finally {\r\n // Clean up temp files\r\n await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});\r\n }\r\n}\r\n\r\n/**\r\n * Extract multiple non-contiguous segments and concatenate them with crossfade\r\n * transitions using FFmpeg xfade/acrossfade filters.\r\n * Falls back to extractCompositeClip if only one segment is provided.\r\n *\r\n * @param transitionDuration Crossfade duration in seconds (default 0.5)\r\n * @param buffer Seconds of padding added before/after each segment (default 1.0)\r\n */\r\nexport async function extractCompositeClipWithTransitions(\r\n videoPath: string,\r\n segments: ShortSegment[],\r\n outputPath: string,\r\n transitionDuration: number = 0.5,\r\n buffer: number = 1.0,\r\n): Promise<string> {\r\n if (!segments || segments.length === 0) {\r\n throw new Error('At least one segment is required');\r\n }\r\n\r\n // Single segment — no transitions needed\r\n if (segments.length === 1) {\r\n return extractClip(videoPath, segments[0].start, segments[0].end, outputPath, buffer);\r\n }\r\n\r\n // Two segments — no transitions needed, use regular composite\r\n if (segments.length === 2 && transitionDuration <= 0) {\r\n return extractCompositeClip(videoPath, segments, outputPath, buffer);\r\n }\r\n\r\n const outputDir = pathMod.dirname(outputPath);\r\n await fs.mkdir(outputDir, { recursive: true });\r\n\r\n // Detect source fps so we can force CFR after trim (FFmpeg 7.x xfade requires it)\r\n const fps = await getVideoFps(videoPath);\r\n\r\n // Build filter_complex for xfade transitions between segments\r\n const filterParts: string[] = [];\r\n const segDurations: number[] = [];\r\n\r\n for (let i = 0; i < segments.length; i++) {\r\n const seg = segments[i];\r\n const bufferedStart = Math.max(0, seg.start - buffer);\r\n const bufferedEnd = seg.end + buffer;\r\n const duration = bufferedEnd - bufferedStart;\r\n segDurations.push(duration);\r\n\r\n filterParts.push(\r\n `[0:v]trim=start=${bufferedStart.toFixed(3)}:end=${bufferedEnd.toFixed(3)},setpts=PTS-STARTPTS,fps=${fps}[v${i}]`,\r\n );\r\n filterParts.push(\r\n `[0:a]atrim=start=${bufferedStart.toFixed(3)}:end=${bufferedEnd.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`,\r\n );\r\n }\r\n\r\n // Chain xfade transitions: [v0][v1]xfade → [xv0]; [xv0][v2]xfade → [xv1]; ...\r\n let prevVideo = 'v0';\r\n let prevAudio = 'a0';\r\n let cumulativeDuration = segDurations[0];\r\n\r\n for (let i = 1; i < segments.length; i++) {\r\n const offset = Math.max(0, cumulativeDuration - transitionDuration);\r\n const outVideo = i === segments.length - 1 ? 'vout' : `xv${i - 1}`;\r\n const outAudio = i === segments.length - 1 ? 'aout' : `xa${i - 1}`;\r\n\r\n filterParts.push(\r\n `[${prevVideo}][v${i}]xfade=transition=fade:duration=${transitionDuration.toFixed(3)}:offset=${offset.toFixed(3)}[${outVideo}]`,\r\n );\r\n filterParts.push(\r\n `[${prevAudio}][a${i}]acrossfade=d=${transitionDuration.toFixed(3)}[${outAudio}]`,\r\n );\r\n\r\n prevVideo = outVideo;\r\n prevAudio = outAudio;\r\n // After xfade, the combined duration shrinks by transitionDuration\r\n cumulativeDuration = cumulativeDuration - transitionDuration + segDurations[i];\r\n }\r\n\r\n const filterComplex = filterParts.join(';\\n');\r\n\r\n const args = [\r\n '-y',\r\n '-i', videoPath,\r\n '-filter_complex', filterComplex,\r\n '-map', '[vout]',\r\n '-map', '[aout]',\r\n '-c:v', 'libx264',\r\n '-preset', 'ultrafast',\r\n '-crf', '23',\r\n '-threads', '4',\r\n '-c:a', 'aac',\r\n '-b:a', '128k',\r\n outputPath,\r\n ];\r\n\r\n logger.info(`[ClipExtraction] Compositing ${segments.length} segments with xfade transitions → ${outputPath}`);\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n execFile(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {\r\n if (error) {\r\n logger.error(`[ClipExtraction] xfade composite failed: ${stderr}`);\r\n reject(new Error(`xfade composite clip failed: ${error.message}`));\r\n return;\r\n }\r\n logger.info(`[ClipExtraction] xfade composite complete: ${outputPath}`);\r\n resolve(outputPath);\r\n });\r\n });\r\n}\r\n","import { execFile } from 'child_process'\r\nimport { promises as fs } from 'fs'\r\nimport pathMod from 'path'\r\nimport os from 'os'\r\nimport { fileURLToPath } from 'url'\r\nimport logger from '../../config/logger'\r\nimport { getFFmpegPath } from '../../config/ffmpegResolver.js'\r\n\r\nconst ffmpegPath = getFFmpegPath()\r\nconst __dirname = pathMod.dirname(fileURLToPath(import.meta.url))\r\nconst FONTS_DIR = pathMod.resolve(__dirname, '..', '..', '..', 'assets', 'fonts')\r\n\r\n/**\r\n * Burn ASS subtitles into video (hard-coded subtitles).\r\n * Uses direct execFile instead of fluent-ffmpeg to avoid Windows path escaping issues.\r\n * Copies the ASS file to a temp dir and uses a relative path to dodge the Windows\r\n * drive-letter colon being parsed as an FFmpeg filter option separator.\r\n */\r\nexport async function burnCaptions(\r\n videoPath: string,\r\n assPath: string,\r\n outputPath: string,\r\n): Promise<string> {\r\n const outputDir = pathMod.dirname(outputPath)\r\n await fs.mkdir(outputDir, { recursive: true })\r\n\r\n logger.info(`Burning captions into video → ${outputPath}`)\r\n\r\n // Create a dedicated temp dir so we can use colon-free relative paths\r\n const workDir = await fs.mkdtemp(pathMod.join(os.tmpdir(), 'caption-'))\r\n const tempAss = pathMod.join(workDir, 'captions.ass')\r\n const tempOutput = pathMod.join(workDir, 'output.mp4')\r\n\r\n await fs.copyFile(assPath, tempAss)\r\n\r\n // Copy bundled fonts so libass can find them via fontsdir=.\r\n const fontFiles = await fs.readdir(FONTS_DIR)\r\n for (const f of fontFiles) {\r\n if (f.endsWith('.ttf') || f.endsWith('.otf')) {\r\n await fs.copyFile(pathMod.join(FONTS_DIR, f), pathMod.join(workDir, f))\r\n }\r\n }\r\n\r\n // Use just the filename — no drive letter, no colons\r\n const args = [\r\n '-y',\r\n '-i', videoPath,\r\n '-vf', 'ass=captions.ass:fontsdir=.',\r\n '-c:a', 'copy',\r\n '-c:v', 'libx264',\r\n '-preset', 'ultrafast',\r\n '-crf', '23',\r\n '-threads', '4',\r\n tempOutput,\r\n ]\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n execFile(ffmpegPath, args, { cwd: workDir, maxBuffer: 10 * 1024 * 1024 }, async (error, _stdout, stderr) => {\r\n const cleanup = async () => {\r\n const files = await fs.readdir(workDir).catch(() => [] as string[])\r\n for (const f of files) {\r\n await fs.unlink(pathMod.join(workDir, f)).catch(() => {})\r\n }\r\n await fs.rmdir(workDir).catch(() => {})\r\n }\r\n\r\n if (error) {\r\n await cleanup()\r\n logger.error(`Caption burning failed: ${stderr || error.message}`)\r\n reject(new Error(`Caption burning failed: ${stderr || error.message}`))\r\n return\r\n }\r\n\r\n try {\r\n await fs.rename(tempOutput, outputPath)\r\n } catch {\r\n await fs.copyFile(tempOutput, outputPath)\r\n }\r\n await cleanup()\r\n logger.info(`Captions burned: ${outputPath}`)\r\n resolve(outputPath)\r\n })\r\n })\r\n}\r\n","import { execFile } from 'child_process'\r\nimport { promises as fs } from 'fs'\r\nimport pathMod from 'path'\r\nimport logger from '../../config/logger'\r\nimport { detectWebcamRegion, getVideoResolution } from './faceDetection'\r\nimport { getFFmpegPath } from '../../config/ffmpegResolver.js'\r\n\r\nconst ffmpegPath = getFFmpegPath()\r\n\r\n// ── Types ────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Supported output aspect ratios.\r\n * - `16:9` — standard landscape (YouTube, desktop)\r\n * - `9:16` — portrait / vertical (TikTok, Reels, Shorts)\r\n * - `1:1` — square (LinkedIn, Twitter)\r\n * - `4:5` — tall feed (Instagram feed)\r\n */\r\nexport type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5'\r\n\r\n/** Social-media platforms we generate video variants for. */\r\nexport type Platform =\r\n | 'tiktok'\r\n | 'youtube-shorts'\r\n | 'instagram-reels'\r\n | 'instagram-feed'\r\n | 'linkedin'\r\n | 'youtube'\r\n | 'twitter'\r\n\r\n/**\r\n * Maps each platform to its preferred aspect ratio.\r\n * Multiple platforms may share a ratio (e.g. TikTok + Reels both use 9:16),\r\n * which lets {@link generatePlatformVariants} deduplicate encodes.\r\n */\r\nexport const PLATFORM_RATIOS: Record<Platform, AspectRatio> = {\r\n 'tiktok': '9:16',\r\n 'youtube-shorts': '9:16',\r\n 'instagram-reels': '9:16',\r\n 'instagram-feed': '4:5',\r\n 'linkedin': '1:1',\r\n 'youtube': '16:9',\r\n 'twitter': '1:1',\r\n}\r\n\r\n/**\r\n * Canonical pixel dimensions for each aspect ratio.\r\n * Width is always 1080 px for non-landscape ratios (the standard vertical\r\n * video width); landscape stays at 1920×1080 for full HD.\r\n */\r\nexport const DIMENSIONS: Record<AspectRatio, { width: number; height: number }> = {\r\n '16:9': { width: 1920, height: 1080 },\r\n '9:16': { width: 1080, height: 1920 },\r\n '1:1': { width: 1080, height: 1080 },\r\n '4:5': { width: 1080, height: 1350 },\r\n}\r\n\r\nexport interface ConvertOptions {\r\n /** Fallback to letterbox/pillarbox instead of cropping (default: false) */\r\n letterbox?: boolean\r\n}\r\n\r\n// ── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Build the FFmpeg `-vf` filter string for a simple center-crop conversion.\r\n *\r\n * This is the **fallback** used when smart layout (webcam detection + split-screen)\r\n * is unavailable. It center-crops the source frame to the target aspect ratio,\r\n * discarding content on the sides (or top/bottom).\r\n *\r\n * **Letterbox mode**: instead of cropping, scales the video to fit inside the\r\n * target dimensions and pads the remaining space with black bars. Useful when\r\n * you don't want to lose any content (e.g. screen recordings with important\r\n * edges).\r\n *\r\n * **Crop formulas** assume a 16:9 landscape source. `ih` = input height,\r\n * `iw` = input width. We compute the crop width from the height to maintain\r\n * the target ratio, then center the crop horizontally.\r\n *\r\n * @param targetRatio - The desired output aspect ratio\r\n * @param letterbox - If true, pad with black bars instead of cropping\r\n * @returns An FFmpeg `-vf` filter string\r\n */\r\nfunction buildCropFilter(targetRatio: AspectRatio, letterbox: boolean): string {\r\n if (letterbox) {\r\n const { width, height } = DIMENSIONS[targetRatio]\r\n // Scale to fit within target dimensions, then pad with black bars\r\n return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`\r\n }\r\n\r\n switch (targetRatio) {\r\n case '9:16':\r\n // Center-crop landscape to portrait: crop width = ih*9/16, keep full height\r\n return 'crop=ih*9/16:ih:(iw-ih*9/16)/2:0,scale=1080:1920'\r\n case '1:1':\r\n // Center-crop to square: use height as the dimension (smaller axis for 16:9)\r\n return 'crop=ih:ih:(iw-ih)/2:0,scale=1080:1080'\r\n case '4:5':\r\n // Center-crop landscape to 4:5: crop width = ih*4/5, keep full height\r\n return 'crop=ih*4/5:ih:(iw-ih*4/5)/2:0,scale=1080:1350'\r\n case '16:9':\r\n // Same ratio — just ensure standard dimensions\r\n return 'scale=1920:1080'\r\n }\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Convert a video's aspect ratio using FFmpeg center-crop.\r\n *\r\n * - 16:9 → 9:16: crops the center column to portrait\r\n * - 16:9 → 1:1: crops to a center square\r\n * - Same ratio: stream-copies without re-encoding\r\n *\r\n * @returns The output path on success\r\n */\r\nexport async function convertAspectRatio(\r\n inputPath: string,\r\n outputPath: string,\r\n targetRatio: AspectRatio,\r\n options: ConvertOptions = {},\r\n): Promise<string> {\r\n const outputDir = pathMod.dirname(outputPath)\r\n await fs.mkdir(outputDir, { recursive: true })\r\n\r\n const sourceRatio: AspectRatio = '16:9' // our videos are always landscape\r\n\r\n // Same ratio — stream copy\r\n if (sourceRatio === targetRatio && !options.letterbox) {\r\n logger.info(`Aspect ratio already ${targetRatio}, copying → ${outputPath}`)\r\n await fs.copyFile(inputPath, outputPath)\r\n return outputPath\r\n }\r\n\r\n const vf = buildCropFilter(targetRatio, options.letterbox ?? false)\r\n logger.info(`Converting aspect ratio to ${targetRatio} (filter: ${vf}) → ${outputPath}`)\r\n\r\n const args = [\r\n '-y',\r\n '-i', inputPath,\r\n '-vf', vf,\r\n '-c:v', 'libx264',\r\n '-preset', 'ultrafast',\r\n '-crf', '23',\r\n '-c:a', 'copy',\r\n '-threads', '4',\r\n outputPath,\r\n ]\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n execFile(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {\r\n if (error) {\r\n logger.error(`Aspect ratio conversion failed: ${stderr || error.message}`)\r\n reject(new Error(`Aspect ratio conversion failed: ${stderr || error.message}`))\r\n return\r\n }\r\n logger.info(`Aspect ratio conversion complete: ${outputPath}`)\r\n resolve(outputPath)\r\n })\r\n })\r\n}\r\n\r\n// ── Smart Layout ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Configuration for the smart split-screen layout.\r\n *\r\n * The split-screen stacks two regions vertically: the **screen content** on top\r\n * and the **webcam face** on the bottom. Each field controls the geometry of\r\n * the final composite:\r\n *\r\n * @property label - Human-readable name for logging (e.g. \"SmartPortrait\")\r\n * @property targetW - Output width in pixels. All smart layouts use 1080 px\r\n * (vertical video standard) so both the screen and cam panels share the\r\n * same width.\r\n * @property screenH - Height of the top panel (screen recording). Combined\r\n * with `camH`, this determines the total output height and the visual\r\n * ratio between screen content and webcam. Roughly ~65% of total height.\r\n * @property camH - Height of the bottom panel (webcam). Roughly ~35% of\r\n * total height. The webcam is AR-matched and center-cropped to fill this\r\n * panel edge-to-edge without black bars.\r\n * @property fallbackRatio - Aspect ratio to use with the simple center-crop\r\n * path ({@link buildCropFilter}) when webcam detection fails.\r\n */\r\ninterface SmartLayoutConfig {\r\n label: string\r\n targetW: number\r\n screenH: number\r\n camH: number\r\n fallbackRatio: AspectRatio\r\n}\r\n\r\n/**\r\n * Shared smart conversion: detects a webcam overlay in the source video and\r\n * builds a **split-screen** layout (screen on top, webcam on bottom).\r\n *\r\n * ### Why split-screen?\r\n * Screen recordings with a webcam overlay (e.g. top-right corner) waste space\r\n * when naively center-cropped to portrait/square. The split-screen approach\r\n * gives the screen content and webcam each their own dedicated panel, making\r\n * both fully visible in a narrow frame.\r\n *\r\n * ### Algorithm\r\n * 1. Run {@link detectWebcamRegion} to find the webcam bounding box.\r\n * 2. **Screen crop**: exclude the webcam columns so only the screen content\r\n * remains, then scale to `targetW × screenH` (letterboxing if needed).\r\n * 3. **Webcam crop**: aspect-ratio-match the webcam region to `targetW × camH`.\r\n * If the webcam is wider than the target, we keep full height and\r\n * center-crop width; if taller, we keep full width and center-crop height.\r\n * This ensures the webcam fills its panel edge-to-edge with **no black bars**.\r\n * 4. **vstack**: vertically stack `[screen][cam]` into the final frame.\r\n *\r\n * Falls back to simple center-crop ({@link buildCropFilter}) if no webcam is\r\n * detected.\r\n *\r\n * @param inputPath - Source video (assumed 16:9 landscape with optional webcam overlay)\r\n * @param outputPath - Destination path for the converted video\r\n * @param config - Layout geometry (see {@link SmartLayoutConfig})\r\n * @returns The output path on success\r\n */\r\nasync function convertWithSmartLayout(\r\n inputPath: string,\r\n outputPath: string,\r\n config: SmartLayoutConfig,\r\n): Promise<string> {\r\n const { label, targetW, screenH, camH, fallbackRatio } = config\r\n const outputDir = pathMod.dirname(outputPath)\r\n await fs.mkdir(outputDir, { recursive: true })\r\n\r\n const webcam = await detectWebcamRegion(inputPath)\r\n\r\n if (!webcam) {\r\n logger.info(`[${label}] No webcam found, falling back to center-crop`)\r\n return convertAspectRatio(inputPath, outputPath, fallbackRatio)\r\n }\r\n\r\n const resolution = await getVideoResolution(inputPath)\r\n\r\n // Determine screen crop region (exclude webcam area using detected bounds)\r\n let screenCropX: number\r\n let screenCropW: number\r\n if (webcam.position === 'top-right' || webcam.position === 'bottom-right') {\r\n screenCropX = 0\r\n screenCropW = webcam.x\r\n } else {\r\n screenCropX = webcam.x + webcam.width\r\n screenCropW = Math.max(0, resolution.width - screenCropX)\r\n }\r\n\r\n // Crop webcam to match target bottom-section aspect ratio, then scale to fill\r\n const targetAR = targetW / camH\r\n const webcamAR = webcam.width / webcam.height\r\n\r\n let faceX: number, faceY: number, faceW: number, faceH: number\r\n if (webcamAR > targetAR) {\r\n // Webcam wider than target: keep full height, center-crop width\r\n faceH = webcam.height\r\n faceW = Math.round(faceH * targetAR)\r\n faceX = webcam.x + Math.round((webcam.width - faceW) / 2)\r\n faceY = webcam.y\r\n } else {\r\n // Webcam taller than target: keep full width, center-crop height\r\n faceW = webcam.width\r\n faceH = Math.round(faceW / targetAR)\r\n faceX = webcam.x\r\n faceY = webcam.y + Math.round((webcam.height - faceH) / 2)\r\n }\r\n\r\n const filterComplex = [\r\n `[0:v]crop=${screenCropW}:ih:${screenCropX}:0,scale=${targetW}:${screenH}:force_original_aspect_ratio=decrease,` +\r\n `pad=${targetW}:${screenH}:(ow-iw)/2:(oh-ih)/2:black[screen]`,\r\n `[0:v]crop=${faceW}:${faceH}:${faceX}:${faceY},scale=${targetW}:${camH}[cam]`,\r\n '[screen][cam]vstack[out]',\r\n ].join(';')\r\n\r\n logger.info(`[${label}] Split-screen layout: webcam at ${webcam.position} → ${outputPath}`)\r\n\r\n const args = [\r\n '-y',\r\n '-i', inputPath,\r\n '-filter_complex', filterComplex,\r\n '-map', '[out]',\r\n '-map', '0:a',\r\n '-c:v', 'libx264',\r\n '-preset', 'ultrafast',\r\n '-crf', '23',\r\n '-c:a', 'aac',\r\n '-b:a', '128k',\r\n '-threads', '4',\r\n outputPath,\r\n ]\r\n\r\n return new Promise<string>((resolve, reject) => {\r\n execFile(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {\r\n if (error) {\r\n logger.error(`[${label}] FFmpeg failed: ${stderr || error.message}`)\r\n reject(new Error(`${label} conversion failed: ${stderr || error.message}`))\r\n return\r\n }\r\n logger.info(`[${label}] Complete: ${outputPath}`)\r\n resolve(outputPath)\r\n })\r\n })\r\n}\r\n\r\n/**\r\n * Smart portrait (9:16) conversion → 1080×1920.\r\n *\r\n * Screen panel: 1080×1248 (65%), Webcam panel: 1080×672 (35%).\r\n * Total: 1080×1920 — standard TikTok / Reels / Shorts dimensions.\r\n *\r\n * Falls back to center-crop 9:16 if no webcam is detected.\r\n *\r\n * @param inputPath - Source landscape video\r\n * @param outputPath - Destination path for the portrait video\r\n */\r\nexport async function convertToPortraitSmart(\r\n inputPath: string,\r\n outputPath: string,\r\n): Promise<string> {\r\n return convertWithSmartLayout(inputPath, outputPath, {\r\n label: 'SmartPortrait',\r\n targetW: 1080,\r\n screenH: 1248,\r\n camH: 672,\r\n fallbackRatio: '9:16',\r\n })\r\n}\r\n\r\n/**\r\n * Smart square (1:1) conversion → 1080×1080.\r\n *\r\n * Screen panel: 1080×700 (65%), Webcam panel: 1080×380 (35%).\r\n * Total: 1080×1080 — standard LinkedIn / Twitter square format.\r\n *\r\n * Falls back to center-crop 1:1 if no webcam is detected.\r\n *\r\n * @param inputPath - Source landscape video\r\n * @param outputPath - Destination path for the square video\r\n */\r\nexport async function convertToSquareSmart(\r\n inputPath: string,\r\n outputPath: string,\r\n): Promise<string> {\r\n return convertWithSmartLayout(inputPath, outputPath, {\r\n label: 'SmartSquare',\r\n targetW: 1080,\r\n screenH: 700,\r\n camH: 380,\r\n fallbackRatio: '1:1',\r\n })\r\n}\r\n\r\n/**\r\n * Smart feed (4:5) conversion → 1080×1350.\r\n *\r\n * Screen panel: 1080×878 (65%), Webcam panel: 1080×472 (35%).\r\n * Total: 1080×1350 — Instagram feed's preferred tall format.\r\n *\r\n * Falls back to center-crop 4:5 if no webcam is detected.\r\n *\r\n * @param inputPath - Source landscape video\r\n * @param outputPath - Destination path for the 4:5 video\r\n */\r\nexport async function convertToFeedSmart(\r\n inputPath: string,\r\n outputPath: string,\r\n): Promise<string> {\r\n return convertWithSmartLayout(inputPath, outputPath, {\r\n label: 'SmartFeed',\r\n targetW: 1080,\r\n screenH: 878,\r\n camH: 472,\r\n fallbackRatio: '4:5',\r\n })\r\n}\r\n\r\n/**\r\n * Generate platform-specific aspect-ratio variants of a short clip.\r\n *\r\n * ### Routing logic\r\n * 1. Maps each requested platform to its aspect ratio via {@link PLATFORM_RATIOS}.\r\n * 2. **Deduplicates by ratio** — if TikTok and Reels both need 9:16, only one\r\n * encode is performed and both platforms reference the same output file.\r\n * 3. Skips 16:9 entirely since the source is already landscape.\r\n * 4. Routes each ratio to its smart converter (portrait / square / feed) for\r\n * split-screen layout, falling back to {@link convertAspectRatio} for any\r\n * ratio without a smart converter.\r\n *\r\n * @param inputPath - Source video (16:9 landscape)\r\n * @param outputDir - Directory to write variant files into\r\n * @param slug - Base filename slug (e.g. \"my-video-short-1\")\r\n * @param platforms - Platforms to generate for (default: tiktok + linkedin)\r\n * @returns Array of variant metadata (one entry per platform, deduplicated files)\r\n */\r\nexport async function generatePlatformVariants(\r\n inputPath: string,\r\n outputDir: string,\r\n slug: string,\r\n platforms: Platform[] = ['tiktok', 'linkedin'],\r\n): Promise<{ platform: Platform; aspectRatio: AspectRatio; path: string; width: number; height: number }[]> {\r\n await fs.mkdir(outputDir, { recursive: true })\r\n\r\n // Deduplicate by aspect ratio to avoid redundant encodes\r\n const ratioMap = new Map<AspectRatio, Platform[]>()\r\n for (const p of platforms) {\r\n const ratio = PLATFORM_RATIOS[p]\r\n if (ratio === '16:9') continue // skip — original is already 16:9\r\n const list = ratioMap.get(ratio) ?? []\r\n list.push(p)\r\n ratioMap.set(ratio, list)\r\n }\r\n\r\n const variants: { platform: Platform; aspectRatio: AspectRatio; path: string; width: number; height: number }[] = []\r\n\r\n for (const [ratio, associatedPlatforms] of ratioMap) {\r\n const suffix = ratio === '9:16' ? 'portrait' : ratio === '4:5' ? 'feed' : 'square'\r\n const outPath = pathMod.join(outputDir, `${slug}-${suffix}.mp4`)\r\n\r\n try {\r\n if (ratio === '9:16') {\r\n await convertToPortraitSmart(inputPath, outPath)\r\n } else if (ratio === '1:1') {\r\n await convertToSquareSmart(inputPath, outPath)\r\n } else if (ratio === '4:5') {\r\n await convertToFeedSmart(inputPath, outPath)\r\n } else {\r\n await convertAspectRatio(inputPath, outPath, ratio)\r\n }\r\n const dims = DIMENSIONS[ratio]\r\n for (const p of associatedPlatforms) {\r\n variants.push({ platform: p, aspectRatio: ratio, path: outPath, width: dims.width, height: dims.height })\r\n }\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err)\r\n logger.warn(`Skipping ${ratio} variant for ${slug}: ${message}`)\r\n }\r\n }\r\n\r\n return variants\r\n}\r\n","import { execFile } from 'child_process'\r\nimport { promises as fs } from 'fs'\r\nimport path from 'path'\r\nimport os from 'os'\r\nimport sharp from 'sharp'\r\nimport logger from '../../config/logger'\r\nimport { getFFmpegPath, getFFprobePath } from '../../config/ffmpegResolver.js'\r\n\r\nconst ffmpegPath = getFFmpegPath()\r\nconst ffprobePath = getFFprobePath()\r\n\r\n// ── Types ────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Bounding box and metadata for a detected webcam overlay in a screen recording.\r\n *\r\n * @property x - Left edge in pixels (original video resolution)\r\n * @property y - Top edge in pixels (original video resolution)\r\n * @property width - Width of the webcam region in pixels\r\n * @property height - Height of the webcam region in pixels\r\n * @property position - Which corner of the frame the webcam occupies\r\n * @property confidence - Detection confidence 0–1 (combines skin-tone consistency\r\n * across frames with per-frame score strength)\r\n */\r\nexport interface WebcamRegion {\r\n x: number\r\n y: number\r\n width: number\r\n height: number\r\n position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\r\n confidence: number\r\n}\r\n\r\n/**\r\n * Per-frame analysis result for a single corner region.\r\n *\r\n * @property skinToneRatio - Fraction of pixels matching the skin-tone heuristic (0–1)\r\n * @property variance - Average RGB channel variance — high variance means the\r\n * region has complex visual content (a face), low variance means a solid\r\n * background or static UI element.\r\n */\r\nexport interface CornerAnalysis {\r\n position: WebcamRegion['position']\r\n x: number\r\n y: number\r\n width: number\r\n height: number\r\n skinToneRatio: number\r\n variance: number\r\n}\r\n\r\n// ── Constants ────────────────────────────────────────────────────────────────\r\n\r\n/** Number of frames sampled evenly across the video for analysis. */\r\nconst SAMPLE_FRAMES = 5\r\n/** Width to downscale frames to for fast pixel analysis. */\r\nconst ANALYSIS_WIDTH = 320\r\n/** Height to downscale frames to for fast pixel analysis. */\r\nconst ANALYSIS_HEIGHT = 180\r\n/** Each corner region is 25% of the frame width/height. */\r\nconst CORNER_FRACTION = 0.25\r\n/** Minimum skin-tone pixel ratio to consider a corner as a webcam candidate. */\r\nconst MIN_SKIN_RATIO = 0.05\r\n/** Minimum confidence score to accept a webcam detection. */\r\nconst MIN_CONFIDENCE = 0.3\r\n\r\n// ── Refinement constants ─────────────────────────────────────────────────────\r\n\r\n/**\r\n * Minimum inter-column/row mean difference to accept as a valid overlay edge.\r\n * The webcam overlay border creates a sharp intensity step between the\r\n * overlay and the screen content behind it. Values below this threshold\r\n * are treated as noise or soft gradients.\r\n */\r\nconst REFINE_MIN_EDGE_DIFF = 3.0\r\n/** Webcam must be at least 5% of the frame in each dimension. */\r\nconst REFINE_MIN_SIZE_FRAC = 0.05\r\n/** Webcam must be at most 55% of the frame in each dimension. */\r\nconst REFINE_MAX_SIZE_FRAC = 0.55\r\n\r\n// ── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\nasync function getVideoDuration(videoPath: string): Promise<number> {\r\n return new Promise((resolve, reject) => {\r\n execFile(\r\n ffprobePath,\r\n ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', videoPath],\r\n (error, stdout) => {\r\n if (error) {\r\n reject(new Error(`ffprobe failed: ${error.message}`))\r\n return\r\n }\r\n resolve(parseFloat(stdout.trim()))\r\n },\r\n )\r\n })\r\n}\r\n\r\nexport async function getVideoResolution(videoPath: string): Promise<{ width: number; height: number }> {\r\n return new Promise((resolve, reject) => {\r\n execFile(\r\n ffprobePath,\r\n [\r\n '-v', 'error',\r\n '-select_streams', 'v:0',\r\n '-show_entries', 'stream=width,height',\r\n '-of', 'csv=p=0',\r\n videoPath,\r\n ],\r\n (error, stdout) => {\r\n if (error) {\r\n reject(new Error(`ffprobe failed: ${error.message}`))\r\n return\r\n }\r\n const [w, h] = stdout.trim().split(',').map(Number)\r\n resolve({ width: w, height: h })\r\n },\r\n )\r\n })\r\n}\r\n\r\nasync function extractSampleFrames(videoPath: string, tempDir: string): Promise<string[]> {\r\n const duration = await getVideoDuration(videoPath)\r\n // Space frames evenly, avoiding very start/end\r\n const interval = Math.max(1, Math.floor(duration / (SAMPLE_FRAMES + 1)))\r\n\r\n const timestamps: number[] = []\r\n for (let i = 1; i <= SAMPLE_FRAMES; i++) {\r\n timestamps.push(i * interval)\r\n }\r\n\r\n const framePaths: string[] = []\r\n for (let i = 0; i < timestamps.length; i++) {\r\n const framePath = path.join(tempDir, `frame_${i}.png`)\r\n framePaths.push(framePath)\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n execFile(\r\n ffmpegPath,\r\n [\r\n '-y',\r\n '-ss', timestamps[i].toFixed(2),\r\n '-i', videoPath,\r\n '-vf', `scale=${ANALYSIS_WIDTH}:${ANALYSIS_HEIGHT}`,\r\n '-frames:v', '1',\r\n '-q:v', '2',\r\n framePath,\r\n ],\r\n { maxBuffer: 10 * 1024 * 1024 },\r\n (error) => {\r\n if (error) {\r\n reject(new Error(`Frame extraction failed at ${timestamps[i]}s: ${error.message}`))\r\n return\r\n }\r\n resolve()\r\n },\r\n )\r\n })\r\n }\r\n\r\n return framePaths\r\n}\r\n\r\n/**\r\n * Check if a pixel (in RGB) falls within skin-tone range.\r\n * Uses simplified HSV heuristic: hue ~0-50°, moderate saturation.\r\n */\r\nexport function isSkinTone(r: number, g: number, b: number): boolean {\r\n // Rule-based skin detection in RGB space (avoids HSV conversion overhead)\r\n // Skin typically: R > 95, G > 40, B > 20, max-min > 15, |R-G| > 15, R > G, R > B\r\n const max = Math.max(r, g, b)\r\n const min = Math.min(r, g, b)\r\n return (\r\n r > 95 && g > 40 && b > 20 &&\r\n (max - min) > 15 &&\r\n Math.abs(r - g) > 15 &&\r\n r > g && r > b\r\n )\r\n}\r\n\r\nasync function analyzeCorner(\r\n framePath: string,\r\n position: WebcamRegion['position'],\r\n): Promise<CornerAnalysis> {\r\n const cornerW = Math.floor(ANALYSIS_WIDTH * CORNER_FRACTION)\r\n const cornerH = Math.floor(ANALYSIS_HEIGHT * CORNER_FRACTION)\r\n\r\n let left: number\r\n let top: number\r\n switch (position) {\r\n case 'top-left': left = 0; top = 0; break\r\n case 'top-right': left = ANALYSIS_WIDTH - cornerW; top = 0; break\r\n case 'bottom-left': left = 0; top = ANALYSIS_HEIGHT - cornerH; break\r\n case 'bottom-right': left = ANALYSIS_WIDTH - cornerW; top = ANALYSIS_HEIGHT - cornerH; break\r\n }\r\n\r\n const { data, info } = await sharp(framePath)\r\n .extract({ left, top, width: cornerW, height: cornerH })\r\n .raw()\r\n .toBuffer({ resolveWithObject: true })\r\n\r\n const totalPixels = info.width * info.height\r\n const channels = info.channels\r\n let skinCount = 0\r\n let sumR = 0, sumG = 0, sumB = 0\r\n let sumR2 = 0, sumG2 = 0, sumB2 = 0\r\n\r\n for (let i = 0; i < data.length; i += channels) {\r\n const r = data[i]\r\n const g = data[i + 1]\r\n const b = data[i + 2]\r\n\r\n if (isSkinTone(r, g, b)) skinCount++\r\n\r\n sumR += r; sumG += g; sumB += b\r\n sumR2 += r * r; sumG2 += g * g; sumB2 += b * b\r\n }\r\n\r\n const skinToneRatio = skinCount / totalPixels\r\n\r\n // Compute variance across all channels as a measure of visual complexity\r\n const meanR = sumR / totalPixels\r\n const meanG = sumG / totalPixels\r\n const meanB = sumB / totalPixels\r\n const varR = sumR2 / totalPixels - meanR * meanR\r\n const varG = sumG2 / totalPixels - meanG * meanG\r\n const varB = sumB2 / totalPixels - meanB * meanB\r\n const variance = (varR + varG + varB) / 3\r\n\r\n return {\r\n position,\r\n x: left,\r\n y: top,\r\n width: cornerW,\r\n height: cornerH,\r\n skinToneRatio,\r\n variance,\r\n }\r\n}\r\n\r\n// ── Refinement helpers ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * Compute per-column mean grayscale intensity over a horizontal band of rows.\r\n *\r\n * Used to find the **vertical edge** of the webcam overlay. Each column gets\r\n * a single mean brightness value averaged over `yFrom..yTo` rows. The\r\n * resulting 1-D signal has a sharp step at the overlay boundary, which\r\n * {@link findPeakDiff} locates.\r\n *\r\n * @param data - Raw pixel buffer (RGB or RGBA interleaved)\r\n * @param width - Image width in pixels\r\n * @param channels - Bytes per pixel (3 for RGB, 4 for RGBA)\r\n * @param yFrom - First row (inclusive)\r\n * @param yTo - Last row (exclusive)\r\n * @returns Float64Array of length `width` with per-column mean grayscale\r\n */\r\nfunction columnMeansForRows(\r\n data: Buffer, width: number, channels: number,\r\n yFrom: number, yTo: number,\r\n): Float64Array {\r\n const means = new Float64Array(width)\r\n const count = yTo - yFrom\r\n if (count <= 0) return means\r\n for (let x = 0; x < width; x++) {\r\n let sum = 0\r\n for (let y = yFrom; y < yTo; y++) {\r\n const idx = (y * width + x) * channels\r\n sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3\r\n }\r\n means[x] = sum / count\r\n }\r\n return means\r\n}\r\n\r\n/**\r\n * Compute per-row mean grayscale intensity over a vertical band of columns.\r\n *\r\n * Used to find the **horizontal edge** of the webcam overlay. Each row gets\r\n * a single mean brightness value averaged over `xFrom..xTo` columns. Works\r\n * the same way as {@link columnMeansForRows} but rotated 90°.\r\n *\r\n * @param data - Raw pixel buffer\r\n * @param width - Image width in pixels\r\n * @param channels - Bytes per pixel\r\n * @param height - Image height in pixels\r\n * @param xFrom - First column (inclusive)\r\n * @param xTo - Last column (exclusive)\r\n * @returns Float64Array of length `height` with per-row mean grayscale\r\n */\r\nfunction rowMeansForCols(\r\n data: Buffer, width: number, channels: number, height: number,\r\n xFrom: number, xTo: number,\r\n): Float64Array {\r\n const means = new Float64Array(height)\r\n const count = xTo - xFrom\r\n if (count <= 0) return means\r\n for (let y = 0; y < height; y++) {\r\n let sum = 0\r\n for (let x = xFrom; x < xTo; x++) {\r\n const idx = (y * width + x) * channels\r\n sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3\r\n }\r\n means[y] = sum / count\r\n }\r\n return means\r\n}\r\n\r\n/** Element-wise average of Float64Arrays. */\r\nfunction averageFloat64Arrays(arrays: Float64Array[]): Float64Array {\r\n if (arrays.length === 0) return new Float64Array(0)\r\n const len = arrays[0].length\r\n const result = new Float64Array(len)\r\n for (const arr of arrays) {\r\n for (let i = 0; i < len; i++) result[i] += arr[i]\r\n }\r\n for (let i = 0; i < len; i++) result[i] /= arrays.length\r\n return result\r\n}\r\n\r\n/**\r\n * Find the position with the largest intensity step between adjacent elements.\r\n *\r\n * \"Peak difference\" = the index where `|means[i+1] - means[i]|` is maximized\r\n * within the search range. This corresponds to the webcam overlay's edge,\r\n * because the overlay border creates a hard brightness transition that\r\n * persists across all frames, while content-based edges average out.\r\n *\r\n * @param means - 1-D array of averaged intensities (from column or row means)\r\n * @param searchFrom - Start of search range (inclusive)\r\n * @param searchTo - End of search range (inclusive)\r\n * @param minDiff - Minimum step magnitude to accept (rejects noise)\r\n * @returns `{index, magnitude}` — index of the edge, or -1 if no edge exceeds minDiff\r\n */\r\nexport function findPeakDiff(\r\n means: Float64Array, searchFrom: number, searchTo: number, minDiff: number,\r\n): { index: number; magnitude: number } {\r\n const lo = Math.max(0, Math.min(searchFrom, searchTo))\r\n const hi = Math.min(means.length - 1, Math.max(searchFrom, searchTo))\r\n let maxDiff = 0\r\n let maxIdx = -1\r\n for (let i = lo; i < hi; i++) {\r\n const d = Math.abs(means[i + 1] - means[i])\r\n if (d > maxDiff) { maxDiff = d; maxIdx = i }\r\n }\r\n return maxDiff >= minDiff ? { index: maxIdx, magnitude: maxDiff } : { index: -1, magnitude: maxDiff }\r\n}\r\n\r\n/**\r\n * Refine the webcam bounding box by detecting the overlay's spatial edges.\r\n *\r\n * ### Why refinement is needed\r\n * The coarse phase ({@link detectWebcamRegion}'s corner analysis) only identifies\r\n * which corner contains a webcam — it uses a fixed 25% region and doesn't know\r\n * the overlay's exact boundaries. Refinement finds pixel-accurate edges.\r\n *\r\n * ### Edge detection algorithm\r\n * 1. For each sample frame, compute **per-column** and **per-row** mean grayscale\r\n * intensities (restricted to the webcam's half of the frame for stronger signal).\r\n * 2. **Average across all frames** — the overlay border is spatially fixed and\r\n * produces a consistent intensity step, while changing video content (slides,\r\n * code, etc.) averages out to a smooth gradient. This is the key insight that\r\n * makes the approach work without traditional edge detection filters.\r\n * 3. Use {@link findPeakDiff} to locate the maximum inter-adjacent intensity\r\n * difference in the averaged signal — this is the overlay's vertical and\r\n * horizontal edge.\r\n * 4. Sanity-check: the resulting rectangle must be 5–55% of the frame in each\r\n * dimension (webcams are never tiny or most of the screen).\r\n *\r\n * @param framePaths - Paths to sample frames at analysis resolution (320×180)\r\n * @param position - Which corner contains the webcam (from coarse phase)\r\n * @returns Refined bounding box in analysis-resolution coordinates, or null\r\n * if no strong edges are found or the result is implausibly sized\r\n */\r\nexport async function refineBoundingBox(\r\n framePaths: string[],\r\n position: WebcamRegion['position'],\r\n): Promise<{ x: number; y: number; width: number; height: number } | null> {\r\n if (framePaths.length === 0) return null\r\n\r\n const isRight = position.includes('right')\r\n const isBottom = position.includes('bottom')\r\n let fw = 0, fh = 0\r\n\r\n const colMeansAll: Float64Array[] = []\r\n const rowMeansAll: Float64Array[] = []\r\n\r\n for (const fp of framePaths) {\r\n const { data, info } = await sharp(fp).raw().toBuffer({ resolveWithObject: true })\r\n fw = info.width; fh = info.height\r\n\r\n // Column means: restrict to rows near the webcam for stronger signal\r\n const yFrom = isBottom ? Math.floor(fh * 0.35) : 0\r\n const yTo = isBottom ? fh : Math.ceil(fh * 0.65)\r\n colMeansAll.push(columnMeansForRows(data, fw, info.channels, yFrom, yTo))\r\n\r\n // Row means: restrict to columns near the webcam\r\n const xFrom = isRight ? Math.floor(fw * 0.35) : 0\r\n const xTo = isRight ? fw : Math.ceil(fw * 0.65)\r\n rowMeansAll.push(rowMeansForCols(data, fw, info.channels, fh, xFrom, xTo))\r\n }\r\n\r\n const avgCols = averageFloat64Arrays(colMeansAll)\r\n const avgRows = averageFloat64Arrays(rowMeansAll)\r\n\r\n // Search for the inner edge in the relevant portion of the frame\r\n const xFrom = isRight ? Math.floor(fw * 0.35) : Math.floor(fw * 0.05)\r\n const xTo = isRight ? Math.floor(fw * 0.95) : Math.floor(fw * 0.65)\r\n const xEdge = findPeakDiff(avgCols, xFrom, xTo, REFINE_MIN_EDGE_DIFF)\r\n\r\n const yFrom = isBottom ? Math.floor(fh * 0.35) : Math.floor(fh * 0.05)\r\n const yTo = isBottom ? Math.floor(fh * 0.95) : Math.floor(fh * 0.65)\r\n const yEdge = findPeakDiff(avgRows, yFrom, yTo, REFINE_MIN_EDGE_DIFF)\r\n\r\n if (xEdge.index < 0 || yEdge.index < 0) {\r\n logger.info(\r\n `[FaceDetection] Edge refinement: no strong edges ` +\r\n `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`,\r\n )\r\n return null\r\n }\r\n\r\n // Build the refined rectangle\r\n let x: number, y: number, w: number, h: number\r\n if (isRight) { x = xEdge.index + 1; w = fw - x }\r\n else { x = 0; w = xEdge.index }\r\n if (isBottom) { y = yEdge.index + 1; h = fh - y }\r\n else { y = 0; h = yEdge.index }\r\n\r\n // Sanity: webcam should be 5-55% of frame in each dimension\r\n if (w < fw * REFINE_MIN_SIZE_FRAC || h < fh * REFINE_MIN_SIZE_FRAC ||\r\n w > fw * REFINE_MAX_SIZE_FRAC || h > fh * REFINE_MAX_SIZE_FRAC) {\r\n logger.info(\r\n `[FaceDetection] Refined bounds implausible ` +\r\n `(${w}x${h} in ${fw}x${fh}), using coarse bounds`,\r\n )\r\n return null\r\n }\r\n\r\n logger.info(\r\n `[FaceDetection] Refined webcam: (${x},${y}) ${w}x${h} at analysis scale ` +\r\n `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`,\r\n )\r\n\r\n return { x, y, width: w, height: h }\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Calculate confidence that a corner contains a webcam overlay based on\r\n * per-frame scores. Combines consistency (fraction of non-zero frames) with\r\n * average score.\r\n */\r\nexport function calculateCornerConfidence(scores: number[]): number {\r\n if (scores.length === 0) return 0\r\n const nonZeroCount = scores.filter(s => s > 0).length\r\n const consistency = nonZeroCount / scores.length\r\n const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length\r\n return consistency * Math.min(avgScore * 10, 1)\r\n}\r\n\r\n/**\r\n * Detect a webcam overlay region in a screen recording.\r\n *\r\n * ### Two-phase approach\r\n *\r\n * **Phase 1 — Coarse corner scan:**\r\n * Samples 5 frames at even intervals across the video and analyzes each of the\r\n * four corners (25% × 25% regions) for skin-tone pixels and visual variance.\r\n * A corner with consistent skin-tone presence across multiple frames is likely\r\n * a webcam overlay. The scoring formula weights skin ratio by variance — webcam\r\n * corners are visually busy (a moving face), while solid-color UI elements\r\n * (like a colored status bar) have low variance even if they match skin tones.\r\n *\r\n * **Phase 2 — Refined edge detection ({@link refineBoundingBox}):**\r\n * Once we know which corner, we find the overlay's exact pixel boundaries by\r\n * looking for persistent intensity edges across frames.\r\n *\r\n * All analysis is performed on downscaled frames (320×180) for speed, then\r\n * results are scaled back to the original video resolution.\r\n *\r\n * @param videoPath - Path to the source video file\r\n * @returns The detected webcam region in original video resolution, or null\r\n * if no webcam overlay is found with sufficient confidence\r\n */\r\nexport async function detectWebcamRegion(videoPath: string): Promise<WebcamRegion | null> {\r\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'face-detect-'))\r\n\r\n try {\r\n const resolution = await getVideoResolution(videoPath)\r\n const framePaths = await extractSampleFrames(videoPath, tempDir)\r\n\r\n const positions: WebcamRegion['position'][] = [\r\n 'top-left', 'top-right', 'bottom-left', 'bottom-right',\r\n ]\r\n\r\n // Analyze all corners across all frames\r\n const scoresByPosition = new Map<WebcamRegion['position'], number[]>()\r\n for (const pos of positions) {\r\n scoresByPosition.set(pos, [])\r\n }\r\n\r\n for (const framePath of framePaths) {\r\n for (const pos of positions) {\r\n const analysis = await analyzeCorner(framePath, pos)\r\n // Score = skin ratio weighted by variance (webcam corners are visually busy)\r\n const score = analysis.skinToneRatio > MIN_SKIN_RATIO\r\n ? analysis.skinToneRatio * Math.min(analysis.variance / 1000, 1)\r\n : 0\r\n scoresByPosition.get(pos)!.push(score)\r\n }\r\n }\r\n\r\n // Find the corner with the highest consistent score\r\n let bestPosition: WebcamRegion['position'] | null = null\r\n let bestConfidence = 0\r\n\r\n for (const [pos, scores] of scoresByPosition) {\r\n const confidence = calculateCornerConfidence(scores)\r\n\r\n if (confidence > bestConfidence) {\r\n bestConfidence = confidence\r\n bestPosition = pos\r\n }\r\n }\r\n\r\n if (!bestPosition || bestConfidence < MIN_CONFIDENCE) {\r\n logger.info('[FaceDetection] No webcam region detected')\r\n return null\r\n }\r\n\r\n // Refine the bounding box using edge detection, then map to original resolution\r\n const refined = await refineBoundingBox(framePaths, bestPosition)\r\n const scaleX = resolution.width / ANALYSIS_WIDTH\r\n const scaleY = resolution.height / ANALYSIS_HEIGHT\r\n\r\n let origX: number, origY: number, origW: number, origH: number\r\n\r\n if (refined) {\r\n origX = Math.round(refined.x * scaleX)\r\n origY = Math.round(refined.y * scaleY)\r\n origW = Math.round(refined.width * scaleX)\r\n origH = Math.round(refined.height * scaleY)\r\n } else {\r\n // Fall back to coarse 25% corner bounds\r\n const cornerW = Math.floor(ANALYSIS_WIDTH * CORNER_FRACTION)\r\n const cornerH = Math.floor(ANALYSIS_HEIGHT * CORNER_FRACTION)\r\n origW = Math.round(cornerW * scaleX)\r\n origH = Math.round(cornerH * scaleY)\r\n switch (bestPosition) {\r\n case 'top-left': origX = 0; origY = 0; break\r\n case 'top-right': origX = resolution.width - origW; origY = 0; break\r\n case 'bottom-left': origX = 0; origY = resolution.height - origH; break\r\n case 'bottom-right':\r\n origX = resolution.width - origW\r\n origY = resolution.height - origH\r\n break\r\n }\r\n }\r\n\r\n const region: WebcamRegion = {\r\n x: origX,\r\n y: origY,\r\n width: origW,\r\n height: origH,\r\n position: bestPosition,\r\n confidence: Math.round(bestConfidence * 100) / 100,\r\n }\r\n\r\n logger.info(\r\n `[FaceDetection] Webcam detected at ${region.position} ` +\r\n `(${region.x},${region.y} ${region.width}x${region.height}) ` +\r\n `confidence=${region.confidence} refined=${!!refined}`,\r\n )\r\n\r\n return region\r\n } finally {\r\n // Clean up temp frames\r\n const files = await fs.readdir(tempDir).catch(() => [] as string[])\r\n for (const f of files) {\r\n await fs.unlink(path.join(tempDir, f)).catch(() => {})\r\n }\r\n await fs.rmdir(tempDir).catch(() => {})\r\n }\r\n}\r\n","import type { ToolWithHandler } from '../providers/types.js'\r\nimport { BaseAgent } from './BaseAgent'\r\nimport { VideoFile, Transcript, ShortClip, ShortSegment, ShortClipVariant } from '../types'\r\nimport { extractClip, extractCompositeClip } from '../tools/ffmpeg/clipExtraction'\r\nimport { generateStyledASSForSegment, generateStyledASSForComposite, generatePortraitASSWithHook, generatePortraitASSWithHookComposite } from '../tools/captions/captionGenerator'\r\nimport { burnCaptions } from '../tools/ffmpeg/captionBurning'\r\nimport { generatePlatformVariants, type Platform } from '../tools/ffmpeg/aspectRatio'\r\nimport { v4 as uuidv4 } from 'uuid'\r\nimport slugify from 'slugify'\r\nimport { promises as fs } from 'fs'\r\nimport path from 'path'\r\nimport logger from '../config/logger'\r\n\r\n// ── Types for the LLM's plan_shorts tool call ──────────────────────────────\r\n\r\ninterface PlannedSegment {\r\n start: number\r\n end: number\r\n description: string\r\n}\r\n\r\ninterface PlannedShort {\r\n title: string\r\n description: string\r\n tags: string[]\r\n segments: PlannedSegment[]\r\n}\r\n\r\n// ── System prompt ───────────────────────────────────────────────────────────\r\n\r\nconst SYSTEM_PROMPT = `You are a short-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and identify the most compelling moments to extract as shorts (15–60 seconds each).\r\n\r\n## What to look for\r\n- **Key insights** — concise, quotable takeaways\r\n- **Funny moments** — humor, wit, unexpected punchlines\r\n- **Controversial takes** — bold opinions that spark discussion\r\n- **Educational nuggets** — clear explanations of complex topics\r\n- **Emotional peaks** — passion, vulnerability, excitement\r\n- **Topic compilations** — multiple brief mentions of one theme that can be stitched together\r\n\r\n## Short types\r\n- **Single segment** — one contiguous section of the video\r\n- **Composite** — multiple non-contiguous segments combined into one short (great for topic compilations or building a narrative arc)\r\n\r\n## Rules\r\n1. Each short must be 15–60 seconds total duration.\r\n2. Timestamps must align to word boundaries from the transcript.\r\n3. Prefer natural sentence boundaries for clean cuts.\r\n4. Aim for 3–8 shorts per video, depending on length and richness.\r\n5. Every short needs a catchy, descriptive title (5–10 words).\r\n6. Tags should be lowercase, no hashes, 3–6 per short.\r\n7. A 1-second buffer is automatically added before and after each segment boundary during extraction, so plan segments based on content timestamps without worrying about clipping words at the edges.\r\n\r\nWhen you have identified the shorts, call the **plan_shorts** tool with your complete plan.`\r\n\r\n// ── JSON Schema for the plan_shorts tool ────────────────────────────────────\r\n\r\nconst PLAN_SHORTS_SCHEMA = {\r\n type: 'object',\r\n properties: {\r\n shorts: {\r\n type: 'array',\r\n description: 'Array of planned short clips',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n title: { type: 'string', description: 'Catchy short title (5–10 words)' },\r\n description: { type: 'string', description: 'Brief description of the short content' },\r\n tags: {\r\n type: 'array',\r\n items: { type: 'string' },\r\n description: 'Lowercase tags without hashes, 3–6 per short',\r\n },\r\n segments: {\r\n type: 'array',\r\n description: 'One or more time segments that compose this short',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n start: { type: 'number', description: 'Start time in seconds' },\r\n end: { type: 'number', description: 'End time in seconds' },\r\n description: { type: 'string', description: 'What happens in this segment' },\r\n },\r\n required: ['start', 'end', 'description'],\r\n },\r\n },\r\n },\r\n required: ['title', 'description', 'tags', 'segments'],\r\n },\r\n },\r\n },\r\n required: ['shorts'],\r\n}\r\n\r\n// ── Agent ────────────────────────────────────────────────────────────────────\r\n\r\nclass ShortsAgent extends BaseAgent {\r\n private plannedShorts: PlannedShort[] = []\r\n\r\n constructor() {\r\n super('ShortsAgent', SYSTEM_PROMPT)\r\n }\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'plan_shorts',\r\n description:\r\n 'Submit the planned shorts as a structured JSON array. Call this once with all planned shorts.',\r\n parameters: PLAN_SHORTS_SCHEMA,\r\n handler: async (args: unknown) => {\r\n return this.handleToolCall('plan_shorts', args as Record<string, unknown>)\r\n },\r\n },\r\n ]\r\n }\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n if (toolName === 'plan_shorts') {\r\n this.plannedShorts = args.shorts as PlannedShort[]\r\n logger.info(`[ShortsAgent] Planned ${this.plannedShorts.length} shorts`)\r\n return { success: true, count: this.plannedShorts.length }\r\n }\r\n throw new Error(`Unknown tool: ${toolName}`)\r\n }\r\n\r\n getPlannedShorts(): PlannedShort[] {\r\n return this.plannedShorts\r\n }\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\nexport async function generateShorts(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n): Promise<ShortClip[]> {\r\n const agent = new ShortsAgent()\r\n\r\n // Build prompt with full transcript including word-level timestamps\r\n const transcriptLines = transcript.segments.map((seg) => {\r\n const words = seg.words\r\n .map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`)\r\n .join(' ')\r\n return `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}\\nWords: ${words}`\r\n })\r\n\r\n const prompt = [\r\n `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and plan shorts.\\n`,\r\n `Video: ${video.filename}`,\r\n `Duration: ${transcript.duration.toFixed(1)}s\\n`,\r\n '--- TRANSCRIPT ---\\n',\r\n transcriptLines.join('\\n\\n'),\r\n '\\n--- END TRANSCRIPT ---',\r\n ].join('\\n')\r\n\r\n try {\r\n await agent.run(prompt)\r\n const planned = agent.getPlannedShorts()\r\n\r\n if (planned.length === 0) {\r\n logger.warn('[ShortsAgent] No shorts were planned')\r\n return []\r\n }\r\n\r\n const shortsDir = path.join(path.dirname(video.repoPath), 'shorts')\r\n await fs.mkdir(shortsDir, { recursive: true })\r\n\r\n const shorts: ShortClip[] = []\r\n\r\n for (const plan of planned) {\r\n const id = uuidv4()\r\n const shortSlug = slugify(plan.title, { lower: true, strict: true })\r\n const totalDuration = plan.segments.reduce((sum, s) => sum + (s.end - s.start), 0)\r\n const outputPath = path.join(shortsDir, `${shortSlug}.mp4`)\r\n\r\n const segments: ShortSegment[] = plan.segments.map((s) => ({\r\n start: s.start,\r\n end: s.end,\r\n description: s.description,\r\n }))\r\n\r\n // Extract the clip (single or composite)\r\n if (segments.length === 1) {\r\n await extractClip(video.repoPath, segments[0].start, segments[0].end, outputPath)\r\n } else {\r\n await extractCompositeClip(video.repoPath, segments, outputPath)\r\n }\r\n\r\n // Generate platform-specific aspect ratio variants from UNCAPTIONED video\r\n // so portrait/square crops are clean before captions are burned per-variant\r\n let variants: ShortClipVariant[] | undefined\r\n try {\r\n const defaultPlatforms: Platform[] = ['tiktok', 'youtube-shorts', 'instagram-reels', 'instagram-feed', 'linkedin']\r\n const results = await generatePlatformVariants(outputPath, shortsDir, shortSlug, defaultPlatforms)\r\n if (results.length > 0) {\r\n variants = results.map((v) => ({\r\n path: v.path,\r\n aspectRatio: v.aspectRatio,\r\n platform: v.platform as ShortClipVariant['platform'],\r\n width: v.width,\r\n height: v.height,\r\n }))\r\n logger.info(`[ShortsAgent] Generated ${variants.length} platform variants for: ${plan.title}`)\r\n }\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err)\r\n logger.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`)\r\n }\r\n\r\n // Generate ASS captions for the landscape short and burn them in\r\n let captionedPath: string | undefined\r\n try {\r\n const assContent = segments.length === 1\r\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end)\r\n : generateStyledASSForComposite(transcript, segments)\r\n\r\n const assPath = path.join(shortsDir, `${shortSlug}.ass`)\r\n await fs.writeFile(assPath, assContent)\r\n\r\n captionedPath = path.join(shortsDir, `${shortSlug}-captioned.mp4`)\r\n await burnCaptions(outputPath, assPath, captionedPath)\r\n logger.info(`[ShortsAgent] Burned captions for short: ${plan.title}`)\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err)\r\n logger.warn(`[ShortsAgent] Caption burning failed for ${plan.title}: ${message}`)\r\n captionedPath = undefined\r\n }\r\n\r\n // Burn portrait-style captions (green highlight, centered, hook overlay) onto portrait variant\r\n if (variants) {\r\n const portraitVariant = variants.find(v => v.aspectRatio === '9:16')\r\n if (portraitVariant) {\r\n try {\r\n const portraitAssContent = segments.length === 1\r\n ? generatePortraitASSWithHook(transcript, plan.title, segments[0].start, segments[0].end)\r\n : generatePortraitASSWithHookComposite(transcript, segments, plan.title)\r\n const portraitAssPath = path.join(shortsDir, `${shortSlug}-portrait.ass`)\r\n await fs.writeFile(portraitAssPath, portraitAssContent)\r\n const portraitCaptionedPath = portraitVariant.path.replace('.mp4', '-captioned.mp4')\r\n await burnCaptions(portraitVariant.path, portraitAssPath, portraitCaptionedPath)\r\n // Update the variant path to point to the captioned version\r\n portraitVariant.path = portraitCaptionedPath\r\n logger.info(`[ShortsAgent] Burned portrait captions with hook for: ${plan.title}`)\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err)\r\n logger.warn(`[ShortsAgent] Portrait caption burning failed for ${plan.title}: ${message}`)\r\n }\r\n }\r\n }\r\n\r\n // Generate description markdown\r\n const mdPath = path.join(shortsDir, `${shortSlug}.md`)\r\n const mdContent = [\r\n `# ${plan.title}\\n`,\r\n plan.description,\r\n '',\r\n '## Segments\\n',\r\n ...plan.segments.map(\r\n (s, i) => `${i + 1}. **${s.start.toFixed(2)}s – ${s.end.toFixed(2)}s** — ${s.description}`,\r\n ),\r\n '',\r\n '## Tags\\n',\r\n plan.tags.map((t) => `- ${t}`).join('\\n'),\r\n '',\r\n ].join('\\n')\r\n await fs.writeFile(mdPath, mdContent)\r\n\r\n shorts.push({\r\n id,\r\n title: plan.title,\r\n slug: shortSlug,\r\n segments,\r\n totalDuration,\r\n outputPath,\r\n captionedPath,\r\n description: plan.description,\r\n tags: plan.tags,\r\n variants,\r\n })\r\n\r\n logger.info(`[ShortsAgent] Created short: ${plan.title} (${totalDuration.toFixed(1)}s)`)\r\n }\r\n\r\n logger.info(`[ShortsAgent] Generated ${shorts.length} shorts`)\r\n return shorts\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n","import type { ToolWithHandler } from '../providers/types.js'\r\nimport { BaseAgent } from './BaseAgent'\r\nimport { VideoFile, Transcript, MediumClip, MediumSegment } from '../types'\r\nimport { extractClip, extractCompositeClipWithTransitions } from '../tools/ffmpeg/clipExtraction'\r\nimport { generateStyledASSForSegment, generateStyledASSForComposite } from '../tools/captions/captionGenerator'\r\nimport { burnCaptions } from '../tools/ffmpeg/captionBurning'\r\nimport { v4 as uuidv4 } from 'uuid'\r\nimport slugify from 'slugify'\r\nimport { promises as fs } from 'fs'\r\nimport path from 'path'\r\nimport logger from '../config/logger'\r\n\r\n// ── Types for the LLM's plan_medium_clips tool call ─────────────────────────\r\n\r\ninterface PlannedSegment {\r\n start: number\r\n end: number\r\n description: string\r\n}\r\n\r\ninterface PlannedMediumClip {\r\n title: string\r\n description: string\r\n tags: string[]\r\n segments: PlannedSegment[]\r\n totalDuration: number\r\n hook: string\r\n topic: string\r\n}\r\n\r\n// ── System prompt ───────────────────────────────────────────────────────────\r\n\r\nconst SYSTEM_PROMPT = `You are a medium-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and identify the best 1–3 minute segments to extract as standalone medium-form clips.\r\n\r\n## What to look for\r\n\r\n- **Complete topics** — a subject is introduced, explored, and concluded\r\n- **Narrative arcs** — problem → solution → result; question → exploration → insight\r\n- **Educational deep dives** — clear, thorough explanations of complex topics\r\n- **Compelling stories** — anecdotes with setup, tension, and resolution\r\n- **Strong arguments** — claim → evidence → implication sequences\r\n- **Topic compilations** — multiple brief mentions of one theme across the video that can be compiled into a cohesive 1–3 minute segment\r\n\r\n## Clip types\r\n\r\n- **Deep Dive** — a single contiguous section (1–3 min) covering one topic in depth\r\n- **Compilation** — multiple non-contiguous segments stitched together around a single theme or narrative thread (1–3 min total)\r\n\r\n## Rules\r\n\r\n1. Each clip must be 60–180 seconds total duration.\r\n2. Timestamps must align to word boundaries from the transcript.\r\n3. Prefer natural sentence and paragraph boundaries for clean entry/exit points.\r\n4. Each clip must be self-contained — a viewer with no other context should understand and get value from the clip.\r\n5. Aim for 2–4 medium clips per video, depending on length and richness.\r\n6. Every clip needs a descriptive title (5–12 words) and a topic label.\r\n7. For compilations, specify segments in the order they should appear in the final clip (which may differ from chronological order).\r\n8. Tags should be lowercase, no hashes, 3–6 per clip.\r\n9. A 1-second buffer is automatically added around each segment boundary.\r\n10. Each clip needs a hook — the opening line or concept that draws viewers in.\r\n\r\n## Differences from shorts\r\n\r\n- Shorts capture *moments*; medium clips capture *complete ideas*.\r\n- Don't just find the most exciting 60 seconds — find where a topic starts and where it naturally concludes.\r\n- It's OK if a medium clip has slower pacing — depth and coherence matter more than constant high energy.\r\n- Look for segments that work as standalone mini-tutorials or explanations.\r\n- Avoid overlap with content that would work better as a short (punchy, viral, single-moment).\r\n\r\nWhen you have identified the clips, call the **plan_medium_clips** tool with your complete plan.`\r\n\r\n// ── JSON Schema for the plan_medium_clips tool ──────────────────────────────\r\n\r\nconst PLAN_MEDIUM_CLIPS_SCHEMA = {\r\n type: 'object',\r\n properties: {\r\n clips: {\r\n type: 'array',\r\n description: 'Array of planned medium-length clips',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n title: { type: 'string', description: 'Descriptive clip title (5–12 words)' },\r\n description: { type: 'string', description: 'Brief description of the clip content' },\r\n tags: {\r\n type: 'array',\r\n items: { type: 'string' },\r\n description: 'Lowercase tags without hashes, 3–6 per clip',\r\n },\r\n segments: {\r\n type: 'array',\r\n description: 'One or more time segments that compose this clip',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n start: { type: 'number', description: 'Start time in seconds' },\r\n end: { type: 'number', description: 'End time in seconds' },\r\n description: { type: 'string', description: 'What happens in this segment' },\r\n },\r\n required: ['start', 'end', 'description'],\r\n },\r\n },\r\n totalDuration: { type: 'number', description: 'Total clip duration in seconds (60–180)' },\r\n hook: { type: 'string', description: 'Opening hook for the clip' },\r\n topic: { type: 'string', description: 'Main topic covered in the clip' },\r\n },\r\n required: ['title', 'description', 'tags', 'segments', 'totalDuration', 'hook', 'topic'],\r\n },\r\n },\r\n },\r\n required: ['clips'],\r\n}\r\n\r\n// ── Agent ────────────────────────────────────────────────────────────────────\r\n\r\nclass MediumVideoAgent extends BaseAgent {\r\n private plannedClips: PlannedMediumClip[] = []\r\n\r\n constructor() {\r\n super('MediumVideoAgent', SYSTEM_PROMPT)\r\n }\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'plan_medium_clips',\r\n description:\r\n 'Submit the planned medium-length clips as a structured JSON array. Call this once with all planned clips.',\r\n parameters: PLAN_MEDIUM_CLIPS_SCHEMA,\r\n handler: async (args: unknown) => {\r\n return this.handleToolCall('plan_medium_clips', args as Record<string, unknown>)\r\n },\r\n },\r\n ]\r\n }\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n if (toolName === 'plan_medium_clips') {\r\n this.plannedClips = args.clips as PlannedMediumClip[]\r\n logger.info(`[MediumVideoAgent] Planned ${this.plannedClips.length} medium clips`)\r\n return { success: true, count: this.plannedClips.length }\r\n }\r\n throw new Error(`Unknown tool: ${toolName}`)\r\n }\r\n\r\n getPlannedClips(): PlannedMediumClip[] {\r\n return this.plannedClips\r\n }\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\nexport async function generateMediumClips(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n): Promise<MediumClip[]> {\r\n const agent = new MediumVideoAgent()\r\n\r\n // Build prompt with full transcript including word-level timestamps\r\n const transcriptLines = transcript.segments.map((seg) => {\r\n const words = seg.words\r\n .map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`)\r\n .join(' ')\r\n return `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}\\nWords: ${words}`\r\n })\r\n\r\n const prompt = [\r\n `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and plan medium-length clips (1–3 minutes each).\\n`,\r\n `Video: ${video.filename}`,\r\n `Duration: ${transcript.duration.toFixed(1)}s\\n`,\r\n '--- TRANSCRIPT ---\\n',\r\n transcriptLines.join('\\n\\n'),\r\n '\\n--- END TRANSCRIPT ---',\r\n ].join('\\n')\r\n\r\n try {\r\n await agent.run(prompt)\r\n const planned = agent.getPlannedClips()\r\n\r\n if (planned.length === 0) {\r\n logger.warn('[MediumVideoAgent] No medium clips were planned')\r\n return []\r\n }\r\n\r\n const clipsDir = path.join(path.dirname(video.repoPath), 'medium-clips')\r\n await fs.mkdir(clipsDir, { recursive: true })\r\n\r\n const clips: MediumClip[] = []\r\n\r\n for (const plan of planned) {\r\n const id = uuidv4()\r\n const clipSlug = slugify(plan.title, { lower: true, strict: true })\r\n const totalDuration = plan.segments.reduce((sum, s) => sum + (s.end - s.start), 0)\r\n const outputPath = path.join(clipsDir, `${clipSlug}.mp4`)\r\n\r\n const segments: MediumSegment[] = plan.segments.map((s) => ({\r\n start: s.start,\r\n end: s.end,\r\n description: s.description,\r\n }))\r\n\r\n // Extract the clip — single segment or composite with crossfade transitions\r\n if (segments.length === 1) {\r\n await extractClip(video.repoPath, segments[0].start, segments[0].end, outputPath)\r\n } else {\r\n await extractCompositeClipWithTransitions(video.repoPath, segments, outputPath)\r\n }\r\n\r\n // Generate ASS captions with medium style (smaller font, bottom-positioned)\r\n let captionedPath: string | undefined\r\n try {\r\n const assContent = segments.length === 1\r\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end, 1.0, 'medium')\r\n : generateStyledASSForComposite(transcript, segments, 1.0, 'medium')\r\n\r\n const assPath = path.join(clipsDir, `${clipSlug}.ass`)\r\n await fs.writeFile(assPath, assContent)\r\n\r\n captionedPath = path.join(clipsDir, `${clipSlug}-captioned.mp4`)\r\n await burnCaptions(outputPath, assPath, captionedPath)\r\n logger.info(`[MediumVideoAgent] Burned captions for clip: ${plan.title}`)\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : String(err)\r\n logger.warn(`[MediumVideoAgent] Caption burning failed for ${plan.title}: ${message}`)\r\n captionedPath = undefined\r\n }\r\n\r\n // Generate description markdown\r\n const mdPath = path.join(clipsDir, `${clipSlug}.md`)\r\n const mdContent = [\r\n `# ${plan.title}\\n`,\r\n `**Topic:** ${plan.topic}\\n`,\r\n `**Hook:** ${plan.hook}\\n`,\r\n plan.description,\r\n '',\r\n '## Segments\\n',\r\n ...plan.segments.map(\r\n (s, i) => `${i + 1}. **${s.start.toFixed(2)}s – ${s.end.toFixed(2)}s** — ${s.description}`,\r\n ),\r\n '',\r\n '## Tags\\n',\r\n plan.tags.map((t) => `- ${t}`).join('\\n'),\r\n '',\r\n ].join('\\n')\r\n await fs.writeFile(mdPath, mdContent)\r\n\r\n clips.push({\r\n id,\r\n title: plan.title,\r\n slug: clipSlug,\r\n segments,\r\n totalDuration,\r\n outputPath,\r\n captionedPath,\r\n description: plan.description,\r\n tags: plan.tags,\r\n hook: plan.hook,\r\n topic: plan.topic,\r\n })\r\n\r\n logger.info(`[MediumVideoAgent] Created medium clip: ${plan.title} (${totalDuration.toFixed(1)}s)`)\r\n }\r\n\r\n logger.info(`[MediumVideoAgent] Generated ${clips.length} medium clips`)\r\n return clips\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n","import type { ToolWithHandler } from '../providers/types.js'\r\nimport * as fs from 'fs'\r\nimport * as path from 'path'\r\nimport { BaseAgent } from './BaseAgent'\r\nimport logger from '../config/logger'\r\nimport { searchWeb } from '../tools/search/exaClient'\r\nimport {\r\n Platform,\r\n ShortClip,\r\n SocialPost,\r\n Transcript,\r\n VideoFile,\r\n VideoSummary,\r\n} from '../types'\r\n\r\n// ── JSON shape the LLM returns via the create_posts tool ────────────────────\r\n\r\ninterface PlatformPost {\r\n platform: string\r\n content: string\r\n hashtags: string[]\r\n links: string[]\r\n characterCount: number\r\n}\r\n\r\ninterface CreatePostsArgs {\r\n posts: PlatformPost[]\r\n}\r\n\r\ninterface SearchLinksArgs {\r\n topics: string[]\r\n}\r\n\r\n// ── System prompt ───────────────────────────────────────────────────────────\r\n\r\nconst SYSTEM_PROMPT = `You are a viral social-media content strategist.\r\nGiven a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.\r\nEach post must match the platform's tone, format, and constraints exactly.\r\n\r\nPlatform guidelines:\r\n1. **TikTok** – Casual, hook-driven, trending hashtags, 150 chars max, emoji-heavy.\r\n2. **YouTube** – Descriptive, SEO-optimized title + description, relevant tags.\r\n3. **Instagram** – Visual storytelling, emoji-rich, 30 hashtags max, engaging caption.\r\n4. **LinkedIn** – Professional, thought-leadership, industry insights, 1-3 hashtags.\r\n5. **X (Twitter)** – Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready.\r\n\r\nIMPORTANT – Content format:\r\nThe \"content\" field you provide must be the FINAL, ready-to-post text that can be directly copied and pasted onto the platform. Do NOT use markdown headers, bullet points, or any formatting inside the content. Include hashtags inline at the end of the post text where appropriate. The content is saved as-is for direct posting.\r\n\r\nWorkflow:\r\n1. First call the \"search_links\" tool with the key topics to find relevant URLs.\r\n2. Then call the \"create_posts\" tool with a JSON object that has a \"posts\" array.\r\n Each element must have: platform, content, hashtags (array), links (array), characterCount.\r\n\r\nInclude relevant links in posts when search results provide them.\r\nAlways call \"create_posts\" exactly once with all 5 platform posts.`\r\n\r\n// ── Agent ────────────────────────────────────────────────────────────────────\r\n\r\nclass SocialMediaAgent extends BaseAgent {\r\n private collectedPosts: PlatformPost[] = []\r\n\r\n constructor() {\r\n super('SocialMediaAgent', SYSTEM_PROMPT)\r\n }\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'search_links',\r\n description:\r\n 'Search for relevant URLs based on topics discussed in the video.',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n topics: {\r\n type: 'array',\r\n items: { type: 'string' },\r\n description: 'List of topics to search for',\r\n },\r\n },\r\n required: ['topics'],\r\n },\r\n handler: async (args: unknown) => {\r\n const { topics } = args as SearchLinksArgs\r\n logger.info(`[SocialMediaAgent] search_links called with topics: ${topics.join(', ')}`)\r\n const allResults: Record<string, { title: string; url: string; snippet: string }[]> = {}\r\n for (const topic of topics) {\r\n allResults[topic] = await searchWeb(topic, 3)\r\n }\r\n return JSON.stringify({ results: allResults })\r\n },\r\n },\r\n {\r\n name: 'create_posts',\r\n description:\r\n 'Submit the generated social media posts for all 5 platforms.',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n posts: {\r\n type: 'array',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n platform: { type: 'string' },\r\n content: { type: 'string' },\r\n hashtags: { type: 'array', items: { type: 'string' } },\r\n links: { type: 'array', items: { type: 'string' } },\r\n characterCount: { type: 'number' },\r\n },\r\n required: ['platform', 'content', 'hashtags', 'links', 'characterCount'],\r\n },\r\n description: 'Array of posts, one per platform',\r\n },\r\n },\r\n required: ['posts'],\r\n },\r\n handler: async (args: unknown) => {\r\n const { posts } = args as CreatePostsArgs\r\n this.collectedPosts = posts\r\n logger.info(`[SocialMediaAgent] create_posts received ${posts.length} posts`)\r\n return JSON.stringify({ success: true, count: posts.length })\r\n },\r\n },\r\n ]\r\n }\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n // Tool dispatch is handled inline via tool handlers above.\r\n // This satisfies the abstract contract from BaseAgent.\r\n logger.warn(`[SocialMediaAgent] Unexpected handleToolCall for \"${toolName}\"`)\r\n return { error: `Unknown tool: ${toolName}` }\r\n }\r\n\r\n getCollectedPosts(): PlatformPost[] {\r\n return this.collectedPosts\r\n }\r\n}\r\n\r\n// ── Helper: map raw platform string → Platform enum ─────────────────────────\r\n\r\nfunction toPlatformEnum(raw: string): Platform {\r\n const normalised = raw.toLowerCase().trim()\r\n switch (normalised) {\r\n case 'tiktok':\r\n return Platform.TikTok\r\n case 'youtube':\r\n return Platform.YouTube\r\n case 'instagram':\r\n return Platform.Instagram\r\n case 'linkedin':\r\n return Platform.LinkedIn\r\n case 'x':\r\n case 'twitter':\r\n return Platform.X\r\n default:\r\n return normalised as Platform\r\n }\r\n}\r\n\r\n// ── Helper: render a post file with YAML frontmatter ───────────────────────\r\n\r\ninterface RenderPostOpts {\r\n videoSlug: string\r\n shortSlug?: string | null\r\n}\r\n\r\nfunction renderPostFile(post: PlatformPost, opts: RenderPostOpts): string {\r\n const now = new Date().toISOString()\r\n const platform = toPlatformEnum(post.platform)\r\n const lines: string[] = ['---']\r\n\r\n lines.push(`platform: ${platform}`)\r\n lines.push(`status: draft`)\r\n lines.push(`scheduledDate: null`)\r\n\r\n if (post.hashtags.length > 0) {\r\n lines.push('hashtags:')\r\n for (const tag of post.hashtags) {\r\n lines.push(` - \"${tag}\"`)\r\n }\r\n } else {\r\n lines.push('hashtags: []')\r\n }\r\n\r\n if (post.links.length > 0) {\r\n lines.push('links:')\r\n for (const link of post.links) {\r\n lines.push(` - url: \"${link}\"`)\r\n lines.push(` title: null`)\r\n }\r\n } else {\r\n lines.push('links: []')\r\n }\r\n\r\n lines.push(`characterCount: ${post.characterCount}`)\r\n lines.push(`videoSlug: \"${opts.videoSlug}\"`)\r\n lines.push(`shortSlug: ${opts.shortSlug ? `\"${opts.shortSlug}\"` : 'null'}`)\r\n lines.push(`createdAt: \"${now}\"`)\r\n lines.push('---')\r\n lines.push('')\r\n lines.push(post.content)\r\n lines.push('')\r\n\r\n return lines.join('\\n')\r\n}\r\n\r\n// ── Public API ──────────────────────────────────────────────────────────────\r\n\r\nexport async function generateShortPosts(\r\n video: VideoFile,\r\n short: ShortClip,\r\n transcript: Transcript,\r\n): Promise<SocialPost[]> {\r\n const agent = new SocialMediaAgent()\r\n\r\n try {\r\n // Extract transcript segments that overlap with the short's time ranges\r\n const relevantText = transcript.segments\r\n .filter((seg) =>\r\n short.segments.some((ss) => seg.start < ss.end && seg.end > ss.start),\r\n )\r\n .map((seg) => seg.text)\r\n .join(' ')\r\n\r\n const userMessage = [\r\n '## Short Clip Metadata',\r\n `- **Title:** ${short.title}`,\r\n `- **Description:** ${short.description}`,\r\n `- **Duration:** ${short.totalDuration.toFixed(1)}s`,\r\n `- **Tags:** ${short.tags.join(', ')}`,\r\n '',\r\n '## Relevant Transcript',\r\n relevantText.slice(0, 3000),\r\n ].join('\\n')\r\n\r\n await agent.run(userMessage)\r\n\r\n const collectedPosts = agent.getCollectedPosts()\r\n\r\n // Save posts to recordings/{slug}/shorts/{short-slug}/posts/\r\n const shortsDir = path.join(path.dirname(video.repoPath), 'shorts')\r\n const postsDir = path.join(shortsDir, short.slug, 'posts')\r\n fs.mkdirSync(postsDir, { recursive: true })\r\n\r\n const socialPosts: SocialPost[] = collectedPosts.map((p) => {\r\n const platform = toPlatformEnum(p.platform)\r\n const outputPath = path.join(postsDir, `${platform}.md`)\r\n\r\n fs.writeFileSync(\r\n outputPath,\r\n renderPostFile(p, { videoSlug: video.slug, shortSlug: short.slug }),\r\n 'utf-8',\r\n )\r\n logger.info(`[SocialMediaAgent] Wrote short post ${outputPath}`)\r\n\r\n return {\r\n platform,\r\n content: p.content,\r\n hashtags: p.hashtags,\r\n links: p.links,\r\n characterCount: p.characterCount,\r\n outputPath,\r\n }\r\n })\r\n\r\n return socialPosts\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n\r\nexport async function generateSocialPosts(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n summary: VideoSummary,\r\n outputDir?: string,\r\n): Promise<SocialPost[]> {\r\n const agent = new SocialMediaAgent()\r\n\r\n try {\r\n // Build the user prompt with transcript summary and metadata\r\n const userMessage = [\r\n '## Video Metadata',\r\n `- **Title:** ${summary.title}`,\r\n `- **Slug:** ${video.slug}`,\r\n `- **Duration:** ${video.duration}s`,\r\n '',\r\n '## Summary',\r\n summary.overview,\r\n '',\r\n '## Key Topics',\r\n summary.keyTopics.map((t) => `- ${t}`).join('\\n'),\r\n '',\r\n '## Transcript (first 3000 chars)',\r\n transcript.text.slice(0, 3000),\r\n ].join('\\n')\r\n\r\n await agent.run(userMessage)\r\n\r\n const collectedPosts = agent.getCollectedPosts()\r\n\r\n // Ensure the output directory exists\r\n const outDir = outputDir ?? path.join(video.videoDir, 'social-posts')\r\n fs.mkdirSync(outDir, { recursive: true })\r\n\r\n const socialPosts: SocialPost[] = collectedPosts.map((p) => {\r\n const platform = toPlatformEnum(p.platform)\r\n const outputPath = path.join(outDir, `${platform}.md`)\r\n\r\n fs.writeFileSync(\r\n outputPath,\r\n renderPostFile(p, { videoSlug: video.slug }),\r\n 'utf-8',\r\n )\r\n logger.info(`[SocialMediaAgent] Wrote ${outputPath}`)\r\n\r\n return {\r\n platform,\r\n content: p.content,\r\n hashtags: p.hashtags,\r\n links: p.links,\r\n characterCount: p.characterCount,\r\n outputPath,\r\n }\r\n })\r\n\r\n return socialPosts\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n","// Exa AI web search client\r\n// Searches the web for relevant links based on topics\r\n\r\nimport Exa from 'exa-js'\r\nimport { getConfig } from '../../config/environment'\r\nimport logger from '../../config/logger'\r\n\r\nexport interface SearchResult {\r\n title: string\r\n url: string\r\n snippet: string\r\n}\r\n\r\nexport async function searchWeb(query: string, numResults: number = 5): Promise<SearchResult[]> {\r\n const config = getConfig()\r\n if (!config.EXA_API_KEY) {\r\n logger.warn('EXA_API_KEY not set — skipping web search')\r\n return []\r\n }\r\n \r\n const exa = new Exa(config.EXA_API_KEY)\r\n \r\n try {\r\n const results = await exa.searchAndContents(query, {\r\n numResults,\r\n text: { maxCharacters: 200 }\r\n })\r\n \r\n return results.results.map(r => ({\r\n title: r.title || '',\r\n url: r.url,\r\n // Exa SDK searchAndContents returns `text` when text option is used, but the type doesn't include it\r\n snippet: (r as unknown as { text?: string }).text || ''\r\n }))\r\n } catch (err) {\r\n logger.error(`Exa search failed: ${err instanceof Error ? err.message : err}`)\r\n return []\r\n }\r\n}\r\n\r\nexport async function searchTopics(topics: string[]): Promise<Map<string, SearchResult[]>> {\r\n const resultsMap = new Map<string, SearchResult[]>()\r\n for (const topic of topics) {\r\n const results = await searchWeb(topic, 3)\r\n resultsMap.set(topic, results)\r\n }\r\n return resultsMap\r\n}\r\n","import type { ToolWithHandler } from '../providers/types.js'\r\nimport * as fs from 'fs'\r\nimport * as path from 'path'\r\nimport { BaseAgent } from './BaseAgent'\r\nimport logger from '../config/logger'\r\nimport { getBrandConfig } from '../config/brand'\r\nimport { searchWeb } from '../tools/search/exaClient'\r\nimport type { Transcript, VideoFile, VideoSummary } from '../types'\r\n\r\n// ── Tool argument shapes ────────────────────────────────────────────────────\r\n\r\ninterface SearchWebArgs {\r\n queries: string[]\r\n}\r\n\r\ninterface WriteBlogArgs {\r\n frontmatter: {\r\n title: string\r\n description: string\r\n tags: string[]\r\n cover_image?: string\r\n }\r\n body: string\r\n}\r\n\r\n// ── Build system prompt from brand config ───────────────────────────────────\r\n\r\nfunction buildSystemPrompt(): string {\r\n const brand = getBrandConfig()\r\n\r\n return `You are a technical blog writer for dev.to, writing from the perspective of ${brand.name} (${brand.handle}).\r\n\r\nVoice & style:\r\n- Tone: ${brand.voice.tone}\r\n- Personality: ${brand.voice.personality}\r\n- Style: ${brand.voice.style}\r\n\r\nContent guidelines: ${brand.contentGuidelines.blogFocus}\r\n\r\nYour task is to generate a full dev.to-style technical blog post (800-1500 words) based on a video transcript and summary.\r\n\r\nThe blog post MUST include:\r\n1. dev.to frontmatter (title, published: false, description, tags, cover_image placeholder)\r\n2. An engaging introduction with a hook\r\n3. Clear sections covering the main content (e.g. The Problem, The Solution, How It Works)\r\n4. Code snippets where the video content discusses code — use fenced code blocks with language tags\r\n5. Key Takeaways section\r\n6. A conclusion\r\n7. A footer referencing the original video\r\n\r\nWorkflow:\r\n1. First call \"search_web\" with key topics to find relevant articles/resources to link to.\r\n2. Then call \"write_blog\" with the complete blog post including frontmatter and body.\r\n - Weave the search result links organically into the post text (don't dump them at the end).\r\n - Reference the video and any shorts naturally.\r\n\r\nAlways call \"write_blog\" exactly once with the complete post.`\r\n}\r\n\r\n// ── Agent ────────────────────────────────────────────────────────────────────\r\n\r\nclass BlogAgent extends BaseAgent {\r\n private blogContent: WriteBlogArgs | null = null\r\n\r\n constructor() {\r\n super('BlogAgent', buildSystemPrompt())\r\n }\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'search_web',\r\n description:\r\n 'Search the web for relevant articles and resources to link in the blog post.',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n queries: {\r\n type: 'array',\r\n items: { type: 'string' },\r\n description: 'List of search queries for finding relevant links',\r\n },\r\n },\r\n required: ['queries'],\r\n },\r\n handler: async (args: unknown) => {\r\n const { queries } = args as SearchWebArgs\r\n logger.info(`[BlogAgent] search_web called with ${queries.length} queries`)\r\n const allResults: Record<string, { title: string; url: string; snippet: string }[]> = {}\r\n for (const query of queries) {\r\n allResults[query] = await searchWeb(query, 3)\r\n }\r\n return JSON.stringify({ results: allResults })\r\n },\r\n },\r\n {\r\n name: 'write_blog',\r\n description:\r\n 'Submit the complete dev.to blog post with frontmatter and markdown body.',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n frontmatter: {\r\n type: 'object',\r\n properties: {\r\n title: { type: 'string' },\r\n description: { type: 'string' },\r\n tags: { type: 'array', items: { type: 'string' } },\r\n cover_image: { type: 'string' },\r\n },\r\n required: ['title', 'description', 'tags'],\r\n },\r\n body: {\r\n type: 'string',\r\n description: 'The full markdown body of the blog post (excluding frontmatter)',\r\n },\r\n },\r\n required: ['frontmatter', 'body'],\r\n },\r\n handler: async (args: unknown) => {\r\n const blogArgs = args as WriteBlogArgs\r\n this.blogContent = blogArgs\r\n logger.info(`[BlogAgent] write_blog received post: \"${blogArgs.frontmatter.title}\"`)\r\n return JSON.stringify({ success: true })\r\n },\r\n },\r\n ]\r\n }\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n _args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n logger.warn(`[BlogAgent] Unexpected handleToolCall for \"${toolName}\"`)\r\n return { error: `Unknown tool: ${toolName}` }\r\n }\r\n\r\n getBlogContent(): WriteBlogArgs | null {\r\n return this.blogContent\r\n }\r\n}\r\n\r\n// ── Render the final markdown ───────────────────────────────────────────────\r\n\r\nfunction renderBlogMarkdown(blog: WriteBlogArgs): string {\r\n const fm = blog.frontmatter\r\n const tags = fm.tags.map((t) => t.toLowerCase().replace(/[^a-z0-9]/g, '')).join(', ')\r\n\r\n const lines: string[] = [\r\n '---',\r\n `title: \"${fm.title}\"`,\r\n 'published: false',\r\n `description: \"${fm.description}\"`,\r\n `tags: ${tags}`,\r\n `cover_image: ${fm.cover_image || ''}`,\r\n '---',\r\n '',\r\n blog.body,\r\n ]\r\n\r\n return lines.join('\\n')\r\n}\r\n\r\n// ── Public API ──────────────────────────────────────────────────────────────\r\n\r\nexport async function generateBlogPost(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n summary: VideoSummary,\r\n): Promise<string> {\r\n const agent = new BlogAgent()\r\n\r\n try {\r\n const userMessage = [\r\n '## Video Metadata',\r\n `- **Title:** ${summary.title}`,\r\n `- **Slug:** ${video.slug}`,\r\n `- **Duration:** ${video.duration}s`,\r\n `- **Recorded:** ${video.createdAt.toISOString().split('T')[0]}`,\r\n '',\r\n '## Summary',\r\n summary.overview,\r\n '',\r\n '## Key Topics',\r\n summary.keyTopics.map((t) => `- ${t}`).join('\\n'),\r\n '',\r\n '## Transcript (first 6000 chars)',\r\n transcript.text.slice(0, 6000),\r\n ].join('\\n')\r\n\r\n await agent.run(userMessage)\r\n\r\n const blogContent = agent.getBlogContent()\r\n if (!blogContent) {\r\n throw new Error('BlogAgent did not produce any blog content')\r\n }\r\n\r\n const outDir = path.join(video.videoDir, 'social-posts')\r\n fs.mkdirSync(outDir, { recursive: true })\r\n\r\n const outputPath = path.join(outDir, 'devto.md')\r\n fs.writeFileSync(outputPath, renderBlogMarkdown(blogContent), 'utf-8')\r\n logger.info(`[BlogAgent] Wrote blog post to ${outputPath}`)\r\n\r\n return outputPath\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n","import type { ToolWithHandler } from '../providers/types.js'\r\nimport { promises as fs } from 'fs'\r\nimport path from 'path'\r\n\r\nimport { BaseAgent } from './BaseAgent'\r\nimport logger from '../config/logger'\r\nimport { getConfig } from '../config/environment'\r\nimport type { VideoFile, Transcript, Chapter } from '../types'\r\n\r\n// ── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\n/** Format seconds → \"M:SS\" or \"H:MM:SS\" for YouTube timestamps */\r\nfunction toYouTubeTimestamp(seconds: number): string {\r\n const h = Math.floor(seconds / 3600)\r\n const m = Math.floor((seconds % 3600) / 60)\r\n const s = Math.floor(seconds % 60)\r\n return h > 0\r\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\r\n : `${m}:${String(s).padStart(2, '0')}`\r\n}\r\n\r\n/** Format seconds → \"MM:SS\" for table display */\r\nfunction fmtTime(seconds: number): string {\r\n const m = Math.floor(seconds / 60)\r\n const s = Math.floor(seconds % 60)\r\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\r\n}\r\n\r\n/** Build a compact transcript block with timestamps for the LLM prompt. */\r\nfunction buildTranscriptBlock(transcript: Transcript): string {\r\n return transcript.segments\r\n .map((seg) => `[${fmtTime(seg.start)} → ${fmtTime(seg.end)}] ${seg.text.trim()}`)\r\n .join('\\n')\r\n}\r\n\r\n// ── Output format generators ─────────────────────────────────────────────────\r\n\r\nfunction generateChaptersJSON(chapters: Chapter[]): string {\r\n return JSON.stringify({ chapters }, null, 2)\r\n}\r\n\r\nfunction generateYouTubeTimestamps(chapters: Chapter[]): string {\r\n return chapters\r\n .map((ch) => `${toYouTubeTimestamp(ch.timestamp)} ${ch.title}`)\r\n .join('\\n')\r\n}\r\n\r\nfunction generateChaptersMarkdown(chapters: Chapter[]): string {\r\n const rows = chapters\r\n .map((ch) => `| ${toYouTubeTimestamp(ch.timestamp)} | ${ch.title} | ${ch.description} |`)\r\n .join('\\n')\r\n\r\n return `## Chapters\r\n\r\n| Time | Chapter | Description |\r\n|------|---------|-------------|\r\n${rows}\r\n`\r\n}\r\n\r\nfunction generateFFMetadata(chapters: Chapter[], totalDuration: number): string {\r\n let meta = ';FFMETADATA1\\n\\n'\r\n for (let i = 0; i < chapters.length; i++) {\r\n const ch = chapters[i]\r\n const startMs = Math.round(ch.timestamp * 1000)\r\n const endMs = i < chapters.length - 1\r\n ? Math.round(chapters[i + 1].timestamp * 1000)\r\n : Math.round(totalDuration * 1000)\r\n const escapedTitle = ch.title.replace(/[=;#\\\\]/g, '\\\\$&')\r\n meta += `[CHAPTER]\\nTIMEBASE=1/1000\\nSTART=${startMs}\\nEND=${endMs}\\ntitle=${escapedTitle}\\n\\n`\r\n }\r\n return meta\r\n}\r\n\r\n// ── System prompt ────────────────────────────────────────────────────────────\r\n\r\nfunction buildChapterSystemPrompt(): string {\r\n return `You are a video chapter generator. Analyze the transcript and identify distinct topic segments.\r\n\r\nRules:\r\n- First chapter MUST start at 0:00\r\n- Minimum 3 chapters, maximum 10\r\n- Each chapter should be 2-5 minutes long\r\n- Chapter titles should be concise (3-7 words)\r\n- Look for topic transitions, \"moving on\", \"next\", \"now let's\", etc.\r\n- Include a brief 1-sentence description per chapter\r\n\r\n**Output format:**\r\nCall the \"generate_chapters\" tool with an array of chapter objects.\r\nEach chapter: { timestamp (seconds from start), title (short, 3-7 words), description (1-sentence summary) }\r\n\r\n**Title style:**\r\n- Use title case: \"Setting Up the Database\"\r\n- Be specific: \"Configuring PostgreSQL\" not \"Database Stuff\"\r\n- Include the action when relevant: \"Building the API Routes\"\r\n- Keep under 50 characters`\r\n}\r\n\r\n// ── Tool argument shape ──────────────────────────────────────────────────────\r\n\r\ninterface GenerateChaptersArgs {\r\n chapters: Chapter[]\r\n}\r\n\r\n// ── ChapterAgent ─────────────────────────────────────────────────────────────\r\n\r\nclass ChapterAgent extends BaseAgent {\r\n private outputDir: string\r\n private totalDuration: number\r\n\r\n constructor(outputDir: string, totalDuration: number) {\r\n super('ChapterAgent', buildChapterSystemPrompt())\r\n this.outputDir = outputDir\r\n this.totalDuration = totalDuration\r\n }\r\n\r\n private get chaptersDir(): string {\r\n return path.join(this.outputDir, 'chapters')\r\n }\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'generate_chapters',\r\n description:\r\n 'Write the identified chapters to disk in all formats. ' +\r\n 'Provide: chapters (array of { timestamp, title, description }).',\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n chapters: {\r\n type: 'array',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n timestamp: { type: 'number', description: 'Seconds from video start' },\r\n title: { type: 'string', description: 'Short chapter title (3-7 words)' },\r\n description: { type: 'string', description: '1-sentence summary' },\r\n },\r\n required: ['timestamp', 'title', 'description'],\r\n },\r\n },\r\n },\r\n required: ['chapters'],\r\n },\r\n handler: async (rawArgs: unknown) => {\r\n const args = rawArgs as GenerateChaptersArgs\r\n return this.handleGenerateChapters(args)\r\n },\r\n },\r\n ]\r\n }\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n switch (toolName) {\r\n case 'generate_chapters':\r\n return this.handleGenerateChapters(args as unknown as GenerateChaptersArgs)\r\n default:\r\n throw new Error(`Unknown tool: ${toolName}`)\r\n }\r\n }\r\n\r\n private async handleGenerateChapters(args: GenerateChaptersArgs): Promise<string> {\r\n const { chapters } = args\r\n await fs.mkdir(this.chaptersDir, { recursive: true })\r\n\r\n // Write all 4 formats in parallel\r\n await Promise.all([\r\n fs.writeFile(\r\n path.join(this.chaptersDir, 'chapters.json'),\r\n generateChaptersJSON(chapters),\r\n 'utf-8',\r\n ),\r\n fs.writeFile(\r\n path.join(this.chaptersDir, 'chapters-youtube.txt'),\r\n generateYouTubeTimestamps(chapters),\r\n 'utf-8',\r\n ),\r\n fs.writeFile(\r\n path.join(this.chaptersDir, 'chapters.md'),\r\n generateChaptersMarkdown(chapters),\r\n 'utf-8',\r\n ),\r\n fs.writeFile(\r\n path.join(this.chaptersDir, 'chapters.ffmetadata'),\r\n generateFFMetadata(chapters, this.totalDuration),\r\n 'utf-8',\r\n ),\r\n ])\r\n\r\n logger.info(`[ChapterAgent] Wrote ${chapters.length} chapters in 4 formats → ${this.chaptersDir}`)\r\n return `Chapters written: ${chapters.length} chapters in 4 formats to ${this.chaptersDir}`\r\n }\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Generate chapters for a video recording.\r\n *\r\n * 1. Creates a ChapterAgent with a `generate_chapters` tool\r\n * 2. Builds a prompt containing the full transcript with timestamps\r\n * 3. Lets the agent analyse the transcript and identify chapter boundaries\r\n * 4. Returns the array of {@link Chapter} objects\r\n */\r\nexport async function generateChapters(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n): Promise<Chapter[]> {\r\n const config = getConfig()\r\n const outputDir = path.join(config.OUTPUT_DIR, video.slug)\r\n\r\n const agent = new ChapterAgent(outputDir, video.duration)\r\n const transcriptBlock = buildTranscriptBlock(transcript)\r\n\r\n const userPrompt = [\r\n `**Video:** ${video.filename}`,\r\n `**Duration:** ${fmtTime(video.duration)} (${Math.round(video.duration)} seconds)`,\r\n '',\r\n '---',\r\n '',\r\n '**Transcript:**',\r\n '',\r\n transcriptBlock,\r\n ].join('\\n')\r\n\r\n let capturedChapters: Chapter[] | undefined\r\n\r\n // Intercept generate_chapters args to capture the result\r\n // Uses `as any` to access private method — required by the intercept-and-capture pattern\r\n const origHandler = (agent as any).handleGenerateChapters.bind(agent) as (\r\n a: GenerateChaptersArgs,\r\n ) => Promise<string>\r\n ;(agent as any).handleGenerateChapters = async (args: GenerateChaptersArgs) => {\r\n capturedChapters = args.chapters\r\n return origHandler(args)\r\n }\r\n\r\n try {\r\n await agent.run(userPrompt)\r\n\r\n if (!capturedChapters) {\r\n throw new Error('ChapterAgent did not call generate_chapters')\r\n }\r\n\r\n return capturedChapters\r\n } finally {\r\n await agent.destroy()\r\n }\r\n}\r\n","import { execSync } from 'child_process'\r\nimport { getConfig } from '../config/environment'\r\nimport logger from '../config/logger'\r\n\r\nexport async function commitAndPush(videoSlug: string, message?: string): Promise<void> {\r\n const { REPO_ROOT } = getConfig()\r\n const commitMessage = message || `Auto-processed video: ${videoSlug}`\r\n\r\n try {\r\n logger.info(`Staging all changes in ${REPO_ROOT}`)\r\n execSync('git add -A', { cwd: REPO_ROOT, stdio: 'pipe' })\r\n\r\n logger.info(`Committing: ${commitMessage}`)\r\n execSync(`git commit -m \"${commitMessage}\"`, { cwd: REPO_ROOT, stdio: 'pipe' })\r\n\r\n const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT, stdio: 'pipe' })\r\n .toString()\r\n .trim()\r\n logger.info(`Pushing to origin ${branch}`)\r\n execSync(`git push origin ${branch}`, { cwd: REPO_ROOT, stdio: 'pipe' })\r\n\r\n logger.info('Git commit and push completed successfully')\r\n } catch (error: unknown) {\r\n const msg = error instanceof Error ? error.message : String(error)\r\n if (msg.includes('nothing to commit')) {\r\n logger.info('Nothing to commit, working tree clean')\r\n return\r\n }\r\n logger.error(`Git operation failed: ${msg}`)\r\n throw error\r\n }\r\n}\r\n\r\nexport async function stageFiles(patterns: string[]): Promise<void> {\r\n const { REPO_ROOT } = getConfig()\r\n\r\n for (const pattern of patterns) {\r\n try {\r\n logger.info(`Staging files matching: ${pattern}`)\r\n execSync(`git add ${pattern}`, { cwd: REPO_ROOT, stdio: 'pipe' })\r\n } catch (error: unknown) {\r\n const msg = error instanceof Error ? error.message : String(error)\r\n logger.error(`Failed to stage pattern \"${pattern}\": ${msg}`)\r\n throw error\r\n }\r\n }\r\n}\r\n","import ffmpeg from 'fluent-ffmpeg'\r\nimport type { ToolWithHandler } from '../providers/types.js'\r\nimport path from 'path'\r\nimport { BaseAgent } from './BaseAgent'\r\nimport { detectSilence, SilenceRegion } from '../tools/ffmpeg/silenceDetection'\r\nimport { singlePassEdit } from '../tools/ffmpeg/singlePassEdit'\r\nimport type { VideoFile, Transcript, SilenceRemovalResult } from '../types'\r\nimport logger from '../config/logger'\r\nimport { getFFmpegPath, getFFprobePath } from '../config/ffmpegResolver.js'\r\n\r\nconst ffmpegPath = getFFmpegPath()\r\nconst ffprobePath = getFFprobePath()\r\nffmpeg.setFfmpegPath(ffmpegPath)\r\nffmpeg.setFfprobePath(ffprobePath)\r\n\r\n// ── Types for the LLM's decide_removals tool call ──────────────────────────\r\n\r\ninterface RemovalDecision {\r\n start: number\r\n end: number\r\n reason: string\r\n}\r\n\r\n// ── System prompt ───────────────────────────────────────────────────────────\r\n\r\nconst SYSTEM_PROMPT = `You are a video editor AI that decides which silent regions in a video should be removed.\r\nYou will receive a transcript with timestamps and a list of detected silence regions.\r\n\r\nBe CONSERVATIVE. Only remove silence that is CLEARLY dead air — no speech, no demonstration, no purpose.\r\nAim to remove no more than 10-15% of total video duration.\r\nWhen in doubt, KEEP the silence.\r\n\r\nKEEP silences that are:\r\n- Dramatic pauses after impactful statements\r\n- Brief thinking pauses (< 2 seconds) in natural speech\r\n- Pauses before important reveals or demonstrations\r\n- Pauses where the speaker is clearly showing something on screen\r\n- Silence during screen demonstrations or typing — the viewer is watching the screen\r\n\r\nREMOVE silences that are:\r\n- Dead air with no purpose (> 3 seconds of nothing)\r\n- Gaps between topics where the speaker was gathering thoughts\r\n- Silence at the very beginning or end of the video\r\n\r\nReturn a JSON array of silence regions to REMOVE (not keep).\r\nWhen you have decided, call the **decide_removals** tool with your removal list.`\r\n\r\n// ── JSON Schema for the decide_removals tool ────────────────────────────────\r\n\r\nconst DECIDE_REMOVALS_SCHEMA = {\r\n type: 'object',\r\n properties: {\r\n removals: {\r\n type: 'array',\r\n description: 'Array of silence regions to remove',\r\n items: {\r\n type: 'object',\r\n properties: {\r\n start: { type: 'number', description: 'Start time in seconds' },\r\n end: { type: 'number', description: 'End time in seconds' },\r\n reason: { type: 'string', description: 'Why this silence should be removed' },\r\n },\r\n required: ['start', 'end', 'reason'],\r\n },\r\n },\r\n },\r\n required: ['removals'],\r\n}\r\n\r\n// ── Agent ────────────────────────────────────────────────────────────────────\r\n\r\nclass SilenceRemovalAgent extends BaseAgent {\r\n private removals: RemovalDecision[] = []\r\n\r\n constructor() {\r\n super('SilenceRemovalAgent', SYSTEM_PROMPT)\r\n }\r\n\r\n protected getTools(): ToolWithHandler[] {\r\n return [\r\n {\r\n name: 'decide_removals',\r\n description:\r\n 'Submit the list of silence regions to remove. Call this once with all removal decisions.',\r\n parameters: DECIDE_REMOVALS_SCHEMA,\r\n handler: async (args: unknown) => {\r\n return this.handleToolCall('decide_removals', args as Record<string, unknown>)\r\n },\r\n },\r\n ]\r\n }\r\n\r\n protected async handleToolCall(\r\n toolName: string,\r\n args: Record<string, unknown>,\r\n ): Promise<unknown> {\r\n if (toolName === 'decide_removals') {\r\n this.removals = args.removals as RemovalDecision[]\r\n logger.info(`[SilenceRemovalAgent] Decided to remove ${this.removals.length} silence regions`)\r\n return { success: true, count: this.removals.length }\r\n }\r\n throw new Error(`Unknown tool: ${toolName}`)\r\n }\r\n\r\n getRemovals(): RemovalDecision[] {\r\n return this.removals\r\n }\r\n}\r\n\r\n// ── FFmpeg helpers ───────────────────────────────────────────────────────────\r\n\r\nfunction getVideoDuration(videoPath: string): Promise<number> {\r\n return new Promise((resolve, reject) => {\r\n ffmpeg.ffprobe(videoPath, (err, metadata) => {\r\n if (err) return reject(new Error(`ffprobe failed: ${err.message}`))\r\n resolve(metadata.format.duration ?? 0)\r\n })\r\n })\r\n}\r\n\r\n// ── Public API ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Detect silence, use the agent to decide context-aware removals, and produce\r\n * an edited video with dead silence removed.\r\n *\r\n * Returns the path to the edited video, or the original path if no edits were needed.\r\n */\r\nexport async function removeDeadSilence(\r\n video: VideoFile,\r\n transcript: Transcript,\r\n): Promise<SilenceRemovalResult> {\r\n const noEdit: SilenceRemovalResult = { editedPath: video.repoPath, removals: [], keepSegments: [], wasEdited: false }\r\n\r\n // 1. Detect silence regions (FFmpeg already filters to >= 0.5s via d=0.5)\r\n const silenceRegions = await detectSilence(video.repoPath, 0.5)\r\n\r\n if (silenceRegions.length === 0) {\r\n logger.info('[SilenceRemoval] No silence regions detected — skipping')\r\n return noEdit\r\n }\r\n\r\n const totalSilence = silenceRegions.reduce((sum, r) => sum + r.duration, 0)\r\n logger.info(`[SilenceRemoval] ${silenceRegions.length} silence regions detected (${totalSilence.toFixed(1)}s total silence)`)\r\n\r\n // Only send silence regions >= 2s to the agent; short pauses are natural speech rhythm\r\n let regionsForAgent = silenceRegions.filter(r => r.duration >= 2)\r\n if (regionsForAgent.length === 0) {\r\n logger.info('[SilenceRemoval] No silence regions >= 2s — skipping')\r\n return noEdit\r\n }\r\n\r\n // Cap at 30 longest regions to fit in context window\r\n if (regionsForAgent.length > 30) {\r\n regionsForAgent = [...regionsForAgent].sort((a, b) => b.duration - a.duration).slice(0, 30)\r\n regionsForAgent.sort((a, b) => a.start - b.start) // restore chronological order\r\n logger.info(`[SilenceRemoval] Capped to top 30 longest regions for agent analysis`)\r\n }\r\n\r\n // 2. Run the agent to decide which silences to remove\r\n const agent = new SilenceRemovalAgent()\r\n\r\n const transcriptLines = transcript.segments.map(\r\n (seg) => `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}`,\r\n )\r\n\r\n const silenceLines = regionsForAgent.map(\r\n (r, i) => `${i + 1}. ${r.start.toFixed(2)}s – ${r.end.toFixed(2)}s (${r.duration.toFixed(2)}s)`,\r\n )\r\n\r\n const prompt = [\r\n `Video: ${video.filename} (${transcript.duration.toFixed(1)}s total)\\n`,\r\n '--- TRANSCRIPT ---\\n',\r\n transcriptLines.join('\\n'),\r\n '\\n--- END TRANSCRIPT ---\\n',\r\n '--- SILENCE REGIONS ---\\n',\r\n silenceLines.join('\\n'),\r\n '\\n--- END SILENCE REGIONS ---\\n',\r\n 'Analyze the context around each silence region and decide which to remove.',\r\n ].join('\\n')\r\n\r\n let removals: RemovalDecision[]\r\n try {\r\n await agent.run(prompt)\r\n removals = agent.getRemovals()\r\n } finally {\r\n await agent.destroy()\r\n }\r\n\r\n if (removals.length === 0) {\r\n logger.info('[SilenceRemoval] Agent decided to keep all silences — skipping edit')\r\n return noEdit\r\n }\r\n\r\n // Safety: cap removals at 20% of video duration\r\n const maxRemoval = transcript.duration * 0.20\r\n let totalRemoval = 0\r\n const cappedRemovals: RemovalDecision[] = []\r\n const byDuration = [...removals].sort((a, b) => (b.end - b.start) - (a.end - a.start))\r\n for (const r of byDuration) {\r\n const dur = r.end - r.start\r\n if (totalRemoval + dur <= maxRemoval) {\r\n cappedRemovals.push(r)\r\n totalRemoval += dur\r\n }\r\n }\r\n if (cappedRemovals.length < removals.length) {\r\n logger.warn(`[SilenceRemoval] Capped from ${removals.length} to ${cappedRemovals.length} regions (${totalRemoval.toFixed(1)}s) to stay under 20% threshold`)\r\n }\r\n removals = cappedRemovals\r\n\r\n if (removals.length === 0) {\r\n logger.info('[SilenceRemoval] All removals exceeded 20% cap — skipping edit')\r\n return noEdit\r\n }\r\n\r\n // 3. Build list of segments to KEEP (inverse of removal regions)\r\n const videoDuration = await getVideoDuration(video.repoPath)\r\n const sortedRemovals = [...removals].sort((a, b) => a.start - b.start)\r\n\r\n const keepSegments: { start: number; end: number }[] = []\r\n let cursor = 0\r\n\r\n for (const removal of sortedRemovals) {\r\n if (removal.start > cursor) {\r\n keepSegments.push({ start: cursor, end: removal.start })\r\n }\r\n cursor = Math.max(cursor, removal.end)\r\n }\r\n\r\n if (cursor < videoDuration) {\r\n keepSegments.push({ start: cursor, end: videoDuration })\r\n }\r\n\r\n if (keepSegments.length === 0) {\r\n logger.warn('[SilenceRemoval] No segments to keep — returning original')\r\n return noEdit\r\n }\r\n\r\n // 4. Single-pass re-encode with trim+setpts+concat for frame-accurate cuts\r\n const editedPath = path.join(video.videoDir, `${video.slug}-edited.mp4`)\r\n await singlePassEdit(video.repoPath, keepSegments, editedPath)\r\n\r\n // Compute effective removals (merged, non-overlapping) from keep-segments\r\n const effectiveRemovals: { start: number; end: number }[] = []\r\n let prevEnd = 0\r\n for (const seg of keepSegments) {\r\n if (seg.start > prevEnd) {\r\n effectiveRemovals.push({ start: prevEnd, end: seg.start })\r\n }\r\n prevEnd = seg.end\r\n }\r\n // Don't add trailing silence as a \"removal\" — it's just the end of the video\r\n\r\n const actualRemoved = effectiveRemovals.reduce((sum, r) => sum + (r.end - r.start), 0)\r\n logger.info(\r\n `[SilenceRemoval] Removed ${effectiveRemovals.length} silence regions (${actualRemoved.toFixed(1)}s). Output: ${editedPath}`,\r\n )\r\n\r\n return {\r\n editedPath,\r\n removals: effectiveRemovals,\r\n keepSegments,\r\n wasEdited: true,\r\n }\r\n}\r\n","import ffmpeg from 'fluent-ffmpeg'\r\nimport logger from '../../config/logger'\r\nimport { getFFmpegPath } from '../../config/ffmpegResolver.js'\r\n\r\nconst ffmpegPath = getFFmpegPath()\r\nffmpeg.setFfmpegPath(ffmpegPath)\r\n\r\nexport interface SilenceRegion {\r\n start: number // seconds\r\n end: number // seconds\r\n duration: number // seconds\r\n}\r\n\r\n/**\r\n * Use FFmpeg silencedetect filter to find silence regions in an audio/video file.\r\n */\r\nexport async function detectSilence(\r\n audioPath: string,\r\n minDuration: number = 1.0,\r\n noiseThreshold: string = '-30dB',\r\n): Promise<SilenceRegion[]> {\r\n logger.info(`Detecting silence in: ${audioPath} (min=${minDuration}s, threshold=${noiseThreshold})`)\r\n\r\n return new Promise<SilenceRegion[]>((resolve, reject) => {\r\n const regions: SilenceRegion[] = []\r\n let stderr = ''\r\n\r\n ffmpeg(audioPath)\r\n .audioFilters(`silencedetect=noise=${noiseThreshold}:d=${minDuration}`)\r\n .format('null')\r\n .output('-')\r\n .on('stderr', (line: string) => {\r\n stderr += line + '\\n'\r\n })\r\n .on('end', () => {\r\n let pendingStart: number | null = null\r\n\r\n for (const line of stderr.split('\\n')) {\r\n const startMatch = line.match(/silence_start:\\s*([\\d.]+)/)\r\n if (startMatch) {\r\n pendingStart = parseFloat(startMatch[1])\r\n }\r\n\r\n const endMatch = line.match(/silence_end:\\s*([\\d.]+)\\s*\\|\\s*silence_duration:\\s*([\\d.]+)/)\r\n if (endMatch) {\r\n const end = parseFloat(endMatch[1])\r\n const duration = parseFloat(endMatch[2])\r\n // When silence starts at t=0, FFmpeg emits silence_end before any silence_start\r\n const start = pendingStart ?? Math.max(0, end - duration)\r\n\r\n regions.push({ start, end, duration })\r\n pendingStart = null\r\n }\r\n }\r\n\r\n const badRegions = regions.filter(r => r.end <= r.start)\r\n if (badRegions.length > 0) {\r\n logger.warn(`[SilenceDetect] Found ${badRegions.length} invalid regions (end <= start) — filtering out`)\r\n }\r\n const validRegions = regions.filter(r => r.end > r.start)\r\n\r\n if (validRegions.length > 0) {\r\n logger.info(`Sample silence regions: ${validRegions.slice(0, 3).map(r => `${r.start.toFixed(1)}s-${r.end.toFixed(1)}s (${r.duration.toFixed(2)}s)`).join(', ')}`)\r\n }\r\n logger.info(`Detected ${validRegions.length} silence regions`)\r\n resolve(validRegions)\r\n })\r\n .on('error', (err) => {\r\n logger.error(`Silence detection failed: ${err.message}`)\r\n reject(new Error(`Silence detection failed: ${err.message}`))\r\n })\r\n .run()\r\n })\r\n}\r\n","import { execFile } from 'child_process'\r\nimport { promises as fs } from 'fs'\r\nimport path from 'path'\r\nimport os from 'os'\r\nimport { fileURLToPath } from 'url'\r\nimport logger from '../../config/logger'\r\nimport { getFFmpegPath } from '../../config/ffmpegResolver.js'\r\n\r\nconst ffmpegPath = getFFmpegPath()\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\r\nconst FONTS_DIR = path.resolve(__dirname, '..', '..', '..', 'assets', 'fonts')\r\n\r\nexport interface KeepSegment {\r\n start: number\r\n end: number\r\n}\r\n\r\n/**\r\n * Build FFmpeg filter_complex string for silence removal.\r\n * Pure function — no I/O, easy to test.\r\n */\r\nexport function buildFilterComplex(\r\n keepSegments: KeepSegment[],\r\n options?: { assFilename?: string; fontsdir?: string },\r\n): string {\r\n if (keepSegments.length === 0) {\r\n throw new Error('keepSegments must not be empty')\r\n }\r\n\r\n const filterParts: string[] = []\r\n const concatInputs: string[] = []\r\n const hasCaptions = options?.assFilename\r\n\r\n for (let i = 0; i < keepSegments.length; i++) {\r\n const seg = keepSegments[i]\r\n filterParts.push(\r\n `[0:v]trim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},setpts=PTS-STARTPTS[v${i}]`,\r\n )\r\n filterParts.push(\r\n `[0:a]atrim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`,\r\n )\r\n concatInputs.push(`[v${i}][a${i}]`)\r\n }\r\n\r\n const concatOutV = hasCaptions ? '[cv]' : '[outv]'\r\n const concatOutA = hasCaptions ? '[ca]' : '[outa]'\r\n\r\n filterParts.push(\r\n `${concatInputs.join('')}concat=n=${keepSegments.length}:v=1:a=1${concatOutV}${concatOutA}`,\r\n )\r\n\r\n if (hasCaptions) {\r\n const fontsdir = options?.fontsdir ?? '.'\r\n filterParts.push(`[cv]ass=${options!.assFilename}:fontsdir=${fontsdir}[outv]`)\r\n }\r\n\r\n return filterParts.join(';\\n')\r\n}\r\n\r\n/**\r\n * Single-pass silence removal using FFmpeg filter_complex.\r\n * Uses trim+setpts+concat for frame-accurate cuts instead of -c copy which\r\n * snaps to keyframes and causes cumulative timestamp drift.\r\n */\r\nexport async function singlePassEdit(\r\n inputPath: string,\r\n keepSegments: KeepSegment[],\r\n outputPath: string,\r\n): Promise<string> {\r\n const filterComplex = buildFilterComplex(keepSegments)\r\n\r\n const args = [\r\n '-y',\r\n '-i', inputPath,\r\n '-filter_complex', filterComplex,\r\n '-map', '[outv]',\r\n '-map', '[outa]',\r\n '-c:v', 'libx264',\r\n '-preset', 'ultrafast',\r\n '-crf', '23',\r\n '-threads', '4',\r\n '-c:a', 'aac',\r\n '-b:a', '128k',\r\n outputPath,\r\n ]\r\n\r\n logger.info(`[SinglePassEdit] Editing ${keepSegments.length} segments → ${outputPath}`)\r\n\r\n return new Promise((resolve, reject) => {\r\n execFile(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {\r\n if (error) {\r\n logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`)\r\n reject(new Error(`Single-pass edit failed: ${error.message}`))\r\n return\r\n }\r\n logger.info(`[SinglePassEdit] Complete: ${outputPath}`)\r\n resolve(outputPath)\r\n })\r\n })\r\n}\r\n\r\n/**\r\n * Single-pass silence removal + caption burning using FFmpeg filter_complex.\r\n * Uses trim+setpts+concat for frame-accurate cuts, then chains ass filter for captions.\r\n * One re-encode, perfect timestamp alignment.\r\n */\r\nexport async function singlePassEditAndCaption(\r\n inputPath: string,\r\n keepSegments: KeepSegment[],\r\n assPath: string,\r\n outputPath: string,\r\n): Promise<string> {\r\n // Copy ASS + bundled fonts to temp dir to avoid Windows drive colon issue\r\n const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'caption-'))\r\n const tempAss = path.join(tempDir, 'captions.ass')\r\n await fs.copyFile(assPath, tempAss)\r\n\r\n const fontFiles = await fs.readdir(FONTS_DIR)\r\n for (const f of fontFiles) {\r\n if (f.endsWith('.ttf') || f.endsWith('.otf')) {\r\n await fs.copyFile(path.join(FONTS_DIR, f), path.join(tempDir, f))\r\n }\r\n }\r\n\r\n const filterComplex = buildFilterComplex(keepSegments, {\r\n assFilename: 'captions.ass',\r\n fontsdir: '.',\r\n })\r\n\r\n const args = [\r\n '-y',\r\n '-i', inputPath,\r\n '-filter_complex', filterComplex,\r\n '-map', '[outv]',\r\n '-map', '[ca]',\r\n '-c:v', 'libx264',\r\n '-preset', 'ultrafast',\r\n '-crf', '23',\r\n '-threads', '4',\r\n '-c:a', 'aac',\r\n '-b:a', '128k',\r\n outputPath,\r\n ]\r\n\r\n logger.info(`[SinglePassEdit] Processing ${keepSegments.length} segments with captions → ${outputPath}`)\r\n\r\n return new Promise((resolve, reject) => {\r\n execFile(ffmpegPath, args, { cwd: tempDir, maxBuffer: 50 * 1024 * 1024 }, async (error, _stdout, stderr) => {\r\n // Cleanup temp\r\n const files = await fs.readdir(tempDir).catch(() => [] as string[])\r\n for (const f of files) {\r\n await fs.unlink(path.join(tempDir, f)).catch(() => {})\r\n }\r\n await fs.rmdir(tempDir).catch(() => {})\r\n\r\n if (error) {\r\n logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`)\r\n reject(new Error(`Single-pass edit failed: ${error.message}`))\r\n return\r\n }\r\n logger.info(`[SinglePassEdit] Complete: ${outputPath}`)\r\n resolve(outputPath)\r\n })\r\n })\r\n}\r\n","import { spawnSync } from 'child_process'\r\nimport { existsSync } from 'fs'\r\nimport { createRequire } from 'module'\r\nimport path from 'path'\r\nimport type { ProviderName } from '../providers/index.js'\r\n\r\nconst require = createRequire(import.meta.url)\r\n\r\ninterface CheckResult {\r\n label: string\r\n ok: boolean\r\n required: boolean\r\n message: string\r\n}\r\n\r\n/** Normalize LLM_PROVIDER the same way the provider factory does. */\r\nexport function normalizeProviderName(raw: string | undefined): string {\r\n return (raw || 'copilot').trim().toLowerCase()\r\n}\r\n\r\nfunction resolveFFmpegPath(): { path: string; source: string } {\r\n if (process.env.FFMPEG_PATH) {\r\n return { path: process.env.FFMPEG_PATH, source: 'FFMPEG_PATH env' }\r\n }\r\n try {\r\n const staticPath = require('ffmpeg-static') as string\r\n if (staticPath && existsSync(staticPath)) {\r\n return { path: staticPath, source: 'ffmpeg-static' }\r\n }\r\n } catch { /* not available */ }\r\n return { path: 'ffmpeg', source: 'system PATH' }\r\n}\r\n\r\nfunction resolveFFprobePath(): { path: string; source: string } {\r\n if (process.env.FFPROBE_PATH) {\r\n return { path: process.env.FFPROBE_PATH, source: 'FFPROBE_PATH env' }\r\n }\r\n try {\r\n const { path: probePath } = require('@ffprobe-installer/ffprobe') as { path: string }\r\n if (probePath && existsSync(probePath)) {\r\n return { path: probePath, source: '@ffprobe-installer/ffprobe' }\r\n }\r\n } catch { /* not available */ }\r\n return { path: 'ffprobe', source: 'system PATH' }\r\n}\r\n\r\nfunction parseVersionFromOutput(output: string): string {\r\n const match = output.match(/(\\d+\\.\\d+(?:\\.\\d+)?)/)\r\n return match ? match[1] : 'unknown'\r\n}\r\n\r\nfunction getFFmpegInstallHint(): string {\r\n const platform = process.platform\r\n const lines = ['Install FFmpeg:']\r\n if (platform === 'win32') {\r\n lines.push(' winget install Gyan.FFmpeg')\r\n lines.push(' choco install ffmpeg (alternative)')\r\n } else if (platform === 'darwin') {\r\n lines.push(' brew install ffmpeg')\r\n } else {\r\n lines.push(' sudo apt install ffmpeg (Debian/Ubuntu)')\r\n lines.push(' sudo dnf install ffmpeg (Fedora)')\r\n lines.push(' sudo pacman -S ffmpeg (Arch)')\r\n }\r\n lines.push(' Or set FFMPEG_PATH to a custom binary location')\r\n return lines.join('\\n ')\r\n}\r\n\r\nfunction checkNode(): CheckResult {\r\n const raw = process.version // e.g. \"v20.11.1\"\r\n const major = parseInt(raw.slice(1), 10)\r\n const ok = major >= 20\r\n return {\r\n label: 'Node.js',\r\n ok,\r\n required: true,\r\n message: ok\r\n ? `Node.js ${raw} (required: ≥20)`\r\n : `Node.js ${raw} — version ≥20 required`,\r\n }\r\n}\r\n\r\nfunction checkFFmpeg(): CheckResult {\r\n const { path: binPath, source } = resolveFFmpegPath()\r\n try {\r\n const result = spawnSync(binPath, ['-version'], { encoding: 'utf-8', timeout: 10_000 })\r\n if (result.status === 0 && result.stdout) {\r\n const ver = parseVersionFromOutput(result.stdout)\r\n return { label: 'FFmpeg', ok: true, required: true, message: `FFmpeg ${ver} (source: ${source})` }\r\n }\r\n } catch { /* spawn failed */ }\r\n return {\r\n label: 'FFmpeg',\r\n ok: false,\r\n required: true,\r\n message: `FFmpeg not found — ${getFFmpegInstallHint()}`,\r\n }\r\n}\r\n\r\nfunction checkFFprobe(): CheckResult {\r\n const { path: binPath, source } = resolveFFprobePath()\r\n try {\r\n const result = spawnSync(binPath, ['-version'], { encoding: 'utf-8', timeout: 10_000 })\r\n if (result.status === 0 && result.stdout) {\r\n const ver = parseVersionFromOutput(result.stdout)\r\n return { label: 'FFprobe', ok: true, required: true, message: `FFprobe ${ver} (source: ${source})` }\r\n }\r\n } catch { /* spawn failed */ }\r\n return {\r\n label: 'FFprobe',\r\n ok: false,\r\n required: true,\r\n message: `FFprobe not found — usually included with FFmpeg.\\n ${getFFmpegInstallHint()}`,\r\n }\r\n}\r\n\r\nfunction checkOpenAIKey(): CheckResult {\r\n const set = !!process.env.OPENAI_API_KEY\r\n return {\r\n label: 'OPENAI_API_KEY',\r\n ok: set,\r\n required: true,\r\n message: set\r\n ? 'OPENAI_API_KEY is set'\r\n : 'OPENAI_API_KEY not set — get one at https://platform.openai.com/api-keys',\r\n }\r\n}\r\n\r\nfunction checkExaKey(): CheckResult {\r\n const set = !!process.env.EXA_API_KEY\r\n return {\r\n label: 'EXA_API_KEY',\r\n ok: set,\r\n required: false,\r\n message: set\r\n ? 'EXA_API_KEY is set'\r\n : 'EXA_API_KEY not set (optional — web search in social posts)',\r\n }\r\n}\r\n\r\nfunction checkGit(): CheckResult {\r\n try {\r\n const result = spawnSync('git', ['--version'], { encoding: 'utf-8', timeout: 10_000 })\r\n if (result.status === 0 && result.stdout) {\r\n const ver = parseVersionFromOutput(result.stdout)\r\n return { label: 'Git', ok: true, required: false, message: `Git ${ver}` }\r\n }\r\n } catch { /* spawn failed */ }\r\n return {\r\n label: 'Git',\r\n ok: false,\r\n required: false,\r\n message: 'Git not found (optional — needed for auto-commit stage)',\r\n }\r\n}\r\n\r\nfunction checkWatchFolder(): CheckResult {\r\n const watchDir = process.env.WATCH_FOLDER || path.join(process.cwd(), 'watch')\r\n const exists = existsSync(watchDir)\r\n return {\r\n label: 'Watch folder',\r\n ok: exists,\r\n required: false,\r\n message: exists\r\n ? `Watch folder exists: ${watchDir}`\r\n : `Watch folder missing: ${watchDir}`,\r\n }\r\n}\r\n\r\nexport function runDoctor(): void {\r\n console.log('\\n🔍 VidPipe Doctor — Checking prerequisites...\\n')\r\n\r\n const results: CheckResult[] = [\r\n checkNode(),\r\n checkFFmpeg(),\r\n checkFFprobe(),\r\n checkOpenAIKey(),\r\n checkExaKey(),\r\n checkGit(),\r\n checkWatchFolder(),\r\n ]\r\n\r\n for (const r of results) {\r\n const icon = r.ok ? '✅' : r.required ? '❌' : '⬚'\r\n console.log(` ${icon} ${r.message}`)\r\n }\r\n\r\n // LLM Provider section — check env vars directly to avoid silent fallback\r\n console.log('\\nLLM Provider')\r\n const providerName = normalizeProviderName(process.env.LLM_PROVIDER) as ProviderName\r\n const isDefault = !process.env.LLM_PROVIDER\r\n const providerLabel = isDefault ? `${providerName} (default)` : providerName\r\n const validProviders: ProviderName[] = ['copilot', 'openai', 'claude']\r\n\r\n if (!validProviders.includes(providerName)) {\r\n console.log(` ❌ Provider: ${providerLabel} — unknown provider`)\r\n results.push({ label: 'LLM Provider', ok: false, required: true, message: `Unknown provider: ${providerName}` })\r\n } else if (providerName === 'copilot') {\r\n console.log(` ✅ Provider: ${providerLabel}`)\r\n console.log(' ✅ Copilot — uses GitHub auth')\r\n } else if (providerName === 'openai') {\r\n console.log(` ✅ Provider: ${providerLabel}`)\r\n if (process.env.OPENAI_API_KEY) {\r\n console.log(' ✅ OPENAI_API_KEY is set (also used for Whisper)')\r\n } else {\r\n console.log(' ❌ OPENAI_API_KEY not set (required for openai provider)')\r\n results.push({ label: 'LLM Provider', ok: false, required: true, message: 'OPENAI_API_KEY not set for OpenAI LLM' })\r\n }\r\n } else if (providerName === 'claude') {\r\n console.log(` ✅ Provider: ${providerLabel}`)\r\n if (process.env.ANTHROPIC_API_KEY) {\r\n console.log(' ✅ ANTHROPIC_API_KEY is set')\r\n } else {\r\n console.log(' ❌ ANTHROPIC_API_KEY not set (required for claude provider)')\r\n results.push({ label: 'LLM Provider', ok: false, required: true, message: 'ANTHROPIC_API_KEY not set for Claude LLM' })\r\n }\r\n }\r\n\r\n const defaultModels: Record<ProviderName, string> = {\r\n copilot: 'Claude Opus 4.6',\r\n openai: 'gpt-4o',\r\n claude: 'claude-opus-4.6',\r\n }\r\n if (validProviders.includes(providerName)) {\r\n const defaultModel = defaultModels[providerName]\r\n const modelOverride = process.env.LLM_MODEL\r\n if (modelOverride) {\r\n console.log(` ℹ️ Model override: ${modelOverride} (default: ${defaultModel})`)\r\n } else {\r\n console.log(` ℹ️ Default model: ${defaultModel}`)\r\n }\r\n }\r\n\r\n const failedRequired = results.filter(r => r.required && !r.ok)\r\n\r\n console.log()\r\n if (failedRequired.length === 0) {\r\n console.log(' All required checks passed! ✅\\n')\r\n process.exit(0)\r\n } else {\r\n console.log(` ${failedRequired.length} required check${failedRequired.length > 1 ? 's' : ''} failed ❌\\n`)\r\n process.exit(1)\r\n }\r\n}\r\n"],"mappings":"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","names":["path","fs","config","fs","path","path","fs","path","fs","require","config","path","fs","path","fsp","ffmpeg","fs","path","ffmpeg","path","fs","fs","fs","config","fs","fs","config","config","path","fsp","path","fsp","text","config","path","fsp","fs","path","config","OpenAI","config","OpenAI","DEFAULT_MODEL","MAX_TOOL_ROUNDS","config","ffmpeg","fs","path","ffmpegPath","ffprobePath","ffmpeg","path","fs","path","fs","config","ffmpeg","fs","ffmpegPath","ffprobePath","ffmpeg","fs","execFile","fs","pathMod","ffmpegPath","pathMod","fs","execFile","execFile","fs","pathMod","execFile","fs","path","os","ffmpegPath","ffprobePath","execFile","path","yFrom","yTo","xFrom","xTo","fs","os","ffmpegPath","pathMod","fs","execFile","config","slugify","fs","path","path","fs","slugify","uuidv4","slugify","fs","path","SYSTEM_PROMPT","path","fs","uuidv4","slugify","fs","path","config","SYSTEM_PROMPT","opts","fs","path","buildSystemPrompt","fs","path","toYouTubeTimestamp","fmtTime","buildTranscriptBlock","path","fs","config","ffmpeg","path","ffmpeg","ffmpegPath","ffmpeg","execFile","fs","path","os","fileURLToPath","ffmpegPath","__dirname","path","fileURLToPath","FONTS_DIR","execFile","fs","os","ffmpegPath","ffprobePath","ffmpeg","SYSTEM_PROMPT","getVideoDuration","path","totalDuration","fs","path","existsSync","createRequire","path","require","path","fileURLToPath","__dirname","config"]}