vidpipe 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +243 -0
  2. package/assets/fonts/Montserrat-Bold.ttf +0 -0
  3. package/assets/fonts/Montserrat-Regular.ttf +0 -0
  4. package/assets/fonts/OFL.txt +93 -0
  5. package/dist/__tests__/agents.test.d.ts +2 -0
  6. package/dist/__tests__/agents.test.d.ts.map +1 -0
  7. package/dist/__tests__/agents.test.js +434 -0
  8. package/dist/__tests__/agents.test.js.map +1 -0
  9. package/dist/__tests__/aspectRatio.test.d.ts +2 -0
  10. package/dist/__tests__/aspectRatio.test.d.ts.map +1 -0
  11. package/dist/__tests__/aspectRatio.test.js +406 -0
  12. package/dist/__tests__/aspectRatio.test.js.map +1 -0
  13. package/dist/__tests__/captionGenerator.test.d.ts +2 -0
  14. package/dist/__tests__/captionGenerator.test.d.ts.map +1 -0
  15. package/dist/__tests__/captionGenerator.test.js +435 -0
  16. package/dist/__tests__/captionGenerator.test.js.map +1 -0
  17. package/dist/__tests__/config.test.d.ts +2 -0
  18. package/dist/__tests__/config.test.d.ts.map +1 -0
  19. package/dist/__tests__/config.test.js +81 -0
  20. package/dist/__tests__/config.test.js.map +1 -0
  21. package/dist/__tests__/faceDetection.test.d.ts +2 -0
  22. package/dist/__tests__/faceDetection.test.d.ts.map +1 -0
  23. package/dist/__tests__/faceDetection.test.js +372 -0
  24. package/dist/__tests__/faceDetection.test.js.map +1 -0
  25. package/dist/__tests__/ffmpegTools.test.d.ts +2 -0
  26. package/dist/__tests__/ffmpegTools.test.d.ts.map +1 -0
  27. package/dist/__tests__/ffmpegTools.test.js +464 -0
  28. package/dist/__tests__/ffmpegTools.test.js.map +1 -0
  29. package/dist/__tests__/integration/captionBurn.test.d.ts +2 -0
  30. package/dist/__tests__/integration/captionBurn.test.d.ts.map +1 -0
  31. package/dist/__tests__/integration/captionBurn.test.js +103 -0
  32. package/dist/__tests__/integration/captionBurn.test.js.map +1 -0
  33. package/dist/__tests__/integration/clipComposite.test.d.ts +2 -0
  34. package/dist/__tests__/integration/clipComposite.test.d.ts.map +1 -0
  35. package/dist/__tests__/integration/clipComposite.test.js +56 -0
  36. package/dist/__tests__/integration/clipComposite.test.js.map +1 -0
  37. package/dist/__tests__/integration/faceDetection.test.d.ts +2 -0
  38. package/dist/__tests__/integration/faceDetection.test.d.ts.map +1 -0
  39. package/dist/__tests__/integration/faceDetection.test.js +85 -0
  40. package/dist/__tests__/integration/faceDetection.test.js.map +1 -0
  41. package/dist/__tests__/integration/ffmpegPipeline.test.d.ts +2 -0
  42. package/dist/__tests__/integration/ffmpegPipeline.test.d.ts.map +1 -0
  43. package/dist/__tests__/integration/ffmpegPipeline.test.js +88 -0
  44. package/dist/__tests__/integration/ffmpegPipeline.test.js.map +1 -0
  45. package/dist/__tests__/integration/fixture.d.ts +19 -0
  46. package/dist/__tests__/integration/fixture.d.ts.map +1 -0
  47. package/dist/__tests__/integration/fixture.js +112 -0
  48. package/dist/__tests__/integration/fixture.js.map +1 -0
  49. package/dist/__tests__/integration/fixture.test.d.ts +2 -0
  50. package/dist/__tests__/integration/fixture.test.d.ts.map +1 -0
  51. package/dist/__tests__/integration/fixture.test.js +27 -0
  52. package/dist/__tests__/integration/fixture.test.js.map +1 -0
  53. package/dist/__tests__/integration/realCaptions.test.d.ts +2 -0
  54. package/dist/__tests__/integration/realCaptions.test.d.ts.map +1 -0
  55. package/dist/__tests__/integration/realCaptions.test.js +226 -0
  56. package/dist/__tests__/integration/realCaptions.test.js.map +1 -0
  57. package/dist/__tests__/integration/realPipeline.test.d.ts +2 -0
  58. package/dist/__tests__/integration/realPipeline.test.d.ts.map +1 -0
  59. package/dist/__tests__/integration/realPipeline.test.js +210 -0
  60. package/dist/__tests__/integration/realPipeline.test.js.map +1 -0
  61. package/dist/__tests__/integration/silenceRemoval.test.d.ts +2 -0
  62. package/dist/__tests__/integration/silenceRemoval.test.d.ts.map +1 -0
  63. package/dist/__tests__/integration/silenceRemoval.test.js +93 -0
  64. package/dist/__tests__/integration/silenceRemoval.test.js.map +1 -0
  65. package/dist/__tests__/pipeline.test.d.ts +2 -0
  66. package/dist/__tests__/pipeline.test.d.ts.map +1 -0
  67. package/dist/__tests__/pipeline.test.js +434 -0
  68. package/dist/__tests__/pipeline.test.js.map +1 -0
  69. package/dist/__tests__/services.test.d.ts +2 -0
  70. package/dist/__tests__/services.test.d.ts.map +1 -0
  71. package/dist/__tests__/services.test.js +655 -0
  72. package/dist/__tests__/services.test.js.map +1 -0
  73. package/dist/__tests__/silenceRemoval.test.d.ts +2 -0
  74. package/dist/__tests__/silenceRemoval.test.d.ts.map +1 -0
  75. package/dist/__tests__/silenceRemoval.test.js +266 -0
  76. package/dist/__tests__/silenceRemoval.test.js.map +1 -0
  77. package/dist/__tests__/singlePassEdit.test.d.ts +2 -0
  78. package/dist/__tests__/singlePassEdit.test.d.ts.map +1 -0
  79. package/dist/__tests__/singlePassEdit.test.js +321 -0
  80. package/dist/__tests__/singlePassEdit.test.js.map +1 -0
  81. package/dist/__tests__/smoke.test.d.ts +2 -0
  82. package/dist/__tests__/smoke.test.d.ts.map +1 -0
  83. package/dist/__tests__/smoke.test.js +8 -0
  84. package/dist/__tests__/smoke.test.js.map +1 -0
  85. package/dist/__tests__/utilities.test.d.ts +2 -0
  86. package/dist/__tests__/utilities.test.d.ts.map +1 -0
  87. package/dist/__tests__/utilities.test.js +268 -0
  88. package/dist/__tests__/utilities.test.js.map +1 -0
  89. package/dist/agents/BaseAgent.d.ts +52 -0
  90. package/dist/agents/BaseAgent.d.ts.map +1 -0
  91. package/dist/agents/BaseAgent.js +108 -0
  92. package/dist/agents/BaseAgent.js.map +1 -0
  93. package/dist/agents/BlogAgent.d.ts +3 -0
  94. package/dist/agents/BlogAgent.d.ts.map +1 -0
  95. package/dist/agents/BlogAgent.js +163 -0
  96. package/dist/agents/BlogAgent.js.map +1 -0
  97. package/dist/agents/ChapterAgent.d.ts +11 -0
  98. package/dist/agents/ChapterAgent.d.ts.map +1 -0
  99. package/dist/agents/ChapterAgent.js +191 -0
  100. package/dist/agents/ChapterAgent.js.map +1 -0
  101. package/dist/agents/MediumVideoAgent.d.ts +3 -0
  102. package/dist/agents/MediumVideoAgent.d.ts.map +1 -0
  103. package/dist/agents/MediumVideoAgent.js +219 -0
  104. package/dist/agents/MediumVideoAgent.js.map +1 -0
  105. package/dist/agents/ShortsAgent.d.ts +3 -0
  106. package/dist/agents/ShortsAgent.d.ts.map +1 -0
  107. package/dist/agents/ShortsAgent.js +243 -0
  108. package/dist/agents/ShortsAgent.js.map +1 -0
  109. package/dist/agents/SilenceRemovalAgent.d.ts +9 -0
  110. package/dist/agents/SilenceRemovalAgent.d.ts.map +1 -0
  111. package/dist/agents/SilenceRemovalAgent.js +208 -0
  112. package/dist/agents/SilenceRemovalAgent.js.map +1 -0
  113. package/dist/agents/SocialMediaAgent.d.ts +4 -0
  114. package/dist/agents/SocialMediaAgent.d.ts.map +1 -0
  115. package/dist/agents/SocialMediaAgent.js +248 -0
  116. package/dist/agents/SocialMediaAgent.js.map +1 -0
  117. package/dist/agents/SummaryAgent.d.ts +11 -0
  118. package/dist/agents/SummaryAgent.d.ts.map +1 -0
  119. package/dist/agents/SummaryAgent.js +333 -0
  120. package/dist/agents/SummaryAgent.js.map +1 -0
  121. package/dist/config/brand.d.ts +29 -0
  122. package/dist/config/brand.d.ts.map +1 -0
  123. package/dist/config/brand.js +83 -0
  124. package/dist/config/brand.js.map +1 -0
  125. package/dist/config/environment.d.ts +36 -0
  126. package/dist/config/environment.d.ts.map +1 -0
  127. package/dist/config/environment.js +44 -0
  128. package/dist/config/environment.js.map +1 -0
  129. package/dist/config/logger.d.ts +5 -0
  130. package/dist/config/logger.d.ts.map +1 -0
  131. package/dist/config/logger.js +13 -0
  132. package/dist/config/logger.js.map +1 -0
  133. package/dist/index.d.ts +2 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +135 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/pipeline.d.ts +57 -0
  138. package/dist/pipeline.d.ts.map +1 -0
  139. package/dist/pipeline.js +287 -0
  140. package/dist/pipeline.js.map +1 -0
  141. package/dist/services/captionGeneration.d.ts +7 -0
  142. package/dist/services/captionGeneration.d.ts.map +1 -0
  143. package/dist/services/captionGeneration.js +29 -0
  144. package/dist/services/captionGeneration.js.map +1 -0
  145. package/dist/services/fileWatcher.d.ts +19 -0
  146. package/dist/services/fileWatcher.d.ts.map +1 -0
  147. package/dist/services/fileWatcher.js +120 -0
  148. package/dist/services/fileWatcher.js.map +1 -0
  149. package/dist/services/gitOperations.d.ts +3 -0
  150. package/dist/services/gitOperations.d.ts.map +1 -0
  151. package/dist/services/gitOperations.js +43 -0
  152. package/dist/services/gitOperations.js.map +1 -0
  153. package/dist/services/socialPosting.d.ts +38 -0
  154. package/dist/services/socialPosting.d.ts.map +1 -0
  155. package/dist/services/socialPosting.js +102 -0
  156. package/dist/services/socialPosting.js.map +1 -0
  157. package/dist/services/transcription.d.ts +3 -0
  158. package/dist/services/transcription.d.ts.map +1 -0
  159. package/dist/services/transcription.js +100 -0
  160. package/dist/services/transcription.js.map +1 -0
  161. package/dist/services/videoIngestion.d.ts +3 -0
  162. package/dist/services/videoIngestion.d.ts.map +1 -0
  163. package/dist/services/videoIngestion.js +103 -0
  164. package/dist/services/videoIngestion.js.map +1 -0
  165. package/dist/tools/captions/captionGenerator.d.ts +84 -0
  166. package/dist/tools/captions/captionGenerator.d.ts.map +1 -0
  167. package/dist/tools/captions/captionGenerator.js +390 -0
  168. package/dist/tools/captions/captionGenerator.js.map +1 -0
  169. package/dist/tools/ffmpeg/aspectRatio.d.ts +101 -0
  170. package/dist/tools/ffmpeg/aspectRatio.d.ts.map +1 -0
  171. package/dist/tools/ffmpeg/aspectRatio.js +338 -0
  172. package/dist/tools/ffmpeg/aspectRatio.js.map +1 -0
  173. package/dist/tools/ffmpeg/audioExtraction.d.ts +16 -0
  174. package/dist/tools/ffmpeg/audioExtraction.d.ts.map +1 -0
  175. package/dist/tools/ffmpeg/audioExtraction.js +86 -0
  176. package/dist/tools/ffmpeg/audioExtraction.js.map +1 -0
  177. package/dist/tools/ffmpeg/captionBurning.d.ts +8 -0
  178. package/dist/tools/ffmpeg/captionBurning.d.ts.map +1 -0
  179. package/dist/tools/ffmpeg/captionBurning.js +71 -0
  180. package/dist/tools/ffmpeg/captionBurning.js.map +1 -0
  181. package/dist/tools/ffmpeg/clipExtraction.d.ts +23 -0
  182. package/dist/tools/ffmpeg/clipExtraction.d.ts.map +1 -0
  183. package/dist/tools/ffmpeg/clipExtraction.js +178 -0
  184. package/dist/tools/ffmpeg/clipExtraction.js.map +1 -0
  185. package/dist/tools/ffmpeg/faceDetection.d.ts +127 -0
  186. package/dist/tools/ffmpeg/faceDetection.d.ts.map +1 -0
  187. package/dist/tools/ffmpeg/faceDetection.js +500 -0
  188. package/dist/tools/ffmpeg/faceDetection.js.map +1 -0
  189. package/dist/tools/ffmpeg/frameCapture.d.ts +10 -0
  190. package/dist/tools/ffmpeg/frameCapture.d.ts.map +1 -0
  191. package/dist/tools/ffmpeg/frameCapture.js +48 -0
  192. package/dist/tools/ffmpeg/frameCapture.js.map +1 -0
  193. package/dist/tools/ffmpeg/silenceDetection.d.ts +10 -0
  194. package/dist/tools/ffmpeg/silenceDetection.d.ts.map +1 -0
  195. package/dist/tools/ffmpeg/silenceDetection.js +55 -0
  196. package/dist/tools/ffmpeg/silenceDetection.js.map +1 -0
  197. package/dist/tools/ffmpeg/singlePassEdit.d.ts +25 -0
  198. package/dist/tools/ffmpeg/singlePassEdit.d.ts.map +1 -0
  199. package/dist/tools/ffmpeg/singlePassEdit.js +123 -0
  200. package/dist/tools/ffmpeg/singlePassEdit.js.map +1 -0
  201. package/dist/tools/search/exaClient.d.ts +8 -0
  202. package/dist/tools/search/exaClient.d.ts.map +1 -0
  203. package/dist/tools/search/exaClient.js +38 -0
  204. package/dist/tools/search/exaClient.js.map +1 -0
  205. package/dist/tools/whisper/whisperClient.d.ts +3 -0
  206. package/dist/tools/whisper/whisperClient.d.ts.map +1 -0
  207. package/dist/tools/whisper/whisperClient.js +77 -0
  208. package/dist/tools/whisper/whisperClient.js.map +1 -0
  209. package/dist/types/index.d.ts +305 -0
  210. package/dist/types/index.d.ts.map +1 -0
  211. package/dist/types/index.js +44 -0
  212. package/dist/types/index.js.map +1 -0
  213. package/package.json +63 -0
@@ -0,0 +1,55 @@
1
+ import ffmpeg from 'fluent-ffmpeg';
2
+ import logger from '../../config/logger';
3
+ const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
4
+ ffmpeg.setFfmpegPath(ffmpegPath);
5
+ /**
6
+ * Use FFmpeg silencedetect filter to find silence regions in an audio/video file.
7
+ */
8
+ export async function detectSilence(audioPath, minDuration = 1.0, noiseThreshold = '-30dB') {
9
+ logger.info(`Detecting silence in: ${audioPath} (min=${minDuration}s, threshold=${noiseThreshold})`);
10
+ return new Promise((resolve, reject) => {
11
+ const regions = [];
12
+ let stderr = '';
13
+ ffmpeg(audioPath)
14
+ .audioFilters(`silencedetect=noise=${noiseThreshold}:d=${minDuration}`)
15
+ .format('null')
16
+ .output('-')
17
+ .on('stderr', (line) => {
18
+ stderr += line + '\n';
19
+ })
20
+ .on('end', () => {
21
+ let pendingStart = null;
22
+ for (const line of stderr.split('\n')) {
23
+ const startMatch = line.match(/silence_start:\s*([\d.]+)/);
24
+ if (startMatch) {
25
+ pendingStart = parseFloat(startMatch[1]);
26
+ }
27
+ const endMatch = line.match(/silence_end:\s*([\d.]+)\s*\|\s*silence_duration:\s*([\d.]+)/);
28
+ if (endMatch) {
29
+ const end = parseFloat(endMatch[1]);
30
+ const duration = parseFloat(endMatch[2]);
31
+ // When silence starts at t=0, FFmpeg emits silence_end before any silence_start
32
+ const start = pendingStart ?? Math.max(0, end - duration);
33
+ regions.push({ start, end, duration });
34
+ pendingStart = null;
35
+ }
36
+ }
37
+ const badRegions = regions.filter(r => r.end <= r.start);
38
+ if (badRegions.length > 0) {
39
+ logger.warn(`[SilenceDetect] Found ${badRegions.length} invalid regions (end <= start) — filtering out`);
40
+ }
41
+ const validRegions = regions.filter(r => r.end > r.start);
42
+ if (validRegions.length > 0) {
43
+ 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(', ')}`);
44
+ }
45
+ logger.info(`Detected ${validRegions.length} silence regions`);
46
+ resolve(validRegions);
47
+ })
48
+ .on('error', (err) => {
49
+ logger.error(`Silence detection failed: ${err.message}`);
50
+ reject(new Error(`Silence detection failed: ${err.message}`));
51
+ })
52
+ .run();
53
+ });
54
+ }
55
+ //# sourceMappingURL=silenceDetection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"silenceDetection.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/silenceDetection.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,eAAe,CAAA;AAClC,OAAO,MAAM,MAAM,qBAAqB,CAAA;AAExC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,QAAQ,CAAA;AACtD,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;AAQhC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,cAAsB,GAAG,EACzB,iBAAyB,OAAO;IAEhC,MAAM,CAAC,IAAI,CAAC,yBAAyB,SAAS,SAAS,WAAW,gBAAgB,cAAc,GAAG,CAAC,CAAA;IAEpG,OAAO,IAAI,OAAO,CAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACtD,MAAM,OAAO,GAAoB,EAAE,CAAA;QACnC,IAAI,MAAM,GAAG,EAAE,CAAA;QAEf,MAAM,CAAC,SAAS,CAAC;aACd,YAAY,CAAC,uBAAuB,cAAc,MAAM,WAAW,EAAE,CAAC;aACtE,MAAM,CAAC,MAAM,CAAC;aACd,MAAM,CAAC,GAAG,CAAC;aACX,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE;YAC7B,MAAM,IAAI,IAAI,GAAG,IAAI,CAAA;QACvB,CAAC,CAAC;aACD,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACd,IAAI,YAAY,GAAkB,IAAI,CAAA;YAEtC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAA;gBAC1D,IAAI,UAAU,EAAE,CAAC;oBACf,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC1C,CAAC;gBAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAA;gBAC1F,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;oBACnC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;oBACxC,gFAAgF;oBAChF,MAAM,KAAK,GAAG,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAA;oBAEzD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAA;oBACtC,YAAY,GAAG,IAAI,CAAA;gBACrB,CAAC;YACH,CAAC;YAED,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAA;YACxD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,yBAAyB,UAAU,CAAC,MAAM,iDAAiD,CAAC,CAAA;YAC1G,CAAC;YACD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;YAEzD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,2BAA2B,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACnK,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,YAAY,YAAY,CAAC,MAAM,kBAAkB,CAAC,CAAA;YAC9D,OAAO,CAAC,YAAY,CAAC,CAAA;QACvB,CAAC,CAAC;aACD,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACnB,MAAM,CAAC,KAAK,CAAC,6BAA6B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;YACxD,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAC/D,CAAC,CAAC;aACD,GAAG,EAAE,CAAA;IACV,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ export interface KeepSegment {
2
+ start: number;
3
+ end: number;
4
+ }
5
+ /**
6
+ * Build FFmpeg filter_complex string for silence removal.
7
+ * Pure function — no I/O, easy to test.
8
+ */
9
+ export declare function buildFilterComplex(keepSegments: KeepSegment[], options?: {
10
+ assFilename?: string;
11
+ fontsdir?: string;
12
+ }): string;
13
+ /**
14
+ * Single-pass silence removal using FFmpeg filter_complex.
15
+ * Uses trim+setpts+concat for frame-accurate cuts instead of -c copy which
16
+ * snaps to keyframes and causes cumulative timestamp drift.
17
+ */
18
+ export declare function singlePassEdit(inputPath: string, keepSegments: KeepSegment[], outputPath: string): Promise<string>;
19
+ /**
20
+ * Single-pass silence removal + caption burning using FFmpeg filter_complex.
21
+ * Uses trim+setpts+concat for frame-accurate cuts, then chains ass filter for captions.
22
+ * One re-encode, perfect timestamp alignment.
23
+ */
24
+ export declare function singlePassEditAndCaption(inputPath: string, keepSegments: KeepSegment[], assPath: string, outputPath: string): Promise<string>;
25
+ //# sourceMappingURL=singlePassEdit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singlePassEdit.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/singlePassEdit.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,EAAE,EAC3B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,MAAM,CAiCR;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,WAAW,EAAE,EAC3B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,WAAW,EAAE,EAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAqDjB"}
@@ -0,0 +1,123 @@
1
+ import { execFile } from 'child_process';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+ import logger from '../../config/logger';
7
+ const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const FONTS_DIR = path.resolve(__dirname, '..', '..', '..', 'assets', 'fonts');
10
+ /**
11
+ * Build FFmpeg filter_complex string for silence removal.
12
+ * Pure function — no I/O, easy to test.
13
+ */
14
+ export function buildFilterComplex(keepSegments, options) {
15
+ if (keepSegments.length === 0) {
16
+ throw new Error('keepSegments must not be empty');
17
+ }
18
+ const filterParts = [];
19
+ const concatInputs = [];
20
+ const hasCaptions = options?.assFilename;
21
+ for (let i = 0; i < keepSegments.length; i++) {
22
+ const seg = keepSegments[i];
23
+ filterParts.push(`[0:v]trim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},setpts=PTS-STARTPTS[v${i}]`);
24
+ filterParts.push(`[0:a]atrim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`);
25
+ concatInputs.push(`[v${i}][a${i}]`);
26
+ }
27
+ const concatOutV = hasCaptions ? '[cv]' : '[outv]';
28
+ const concatOutA = hasCaptions ? '[ca]' : '[outa]';
29
+ filterParts.push(`${concatInputs.join('')}concat=n=${keepSegments.length}:v=1:a=1${concatOutV}${concatOutA}`);
30
+ if (hasCaptions) {
31
+ const fontsdir = options?.fontsdir ?? '.';
32
+ filterParts.push(`[cv]ass=${options.assFilename}:fontsdir=${fontsdir}[outv]`);
33
+ }
34
+ return filterParts.join(';\n');
35
+ }
36
+ /**
37
+ * Single-pass silence removal using FFmpeg filter_complex.
38
+ * Uses trim+setpts+concat for frame-accurate cuts instead of -c copy which
39
+ * snaps to keyframes and causes cumulative timestamp drift.
40
+ */
41
+ export async function singlePassEdit(inputPath, keepSegments, outputPath) {
42
+ const filterComplex = buildFilterComplex(keepSegments);
43
+ const args = [
44
+ '-y',
45
+ '-i', inputPath,
46
+ '-filter_complex', filterComplex,
47
+ '-map', '[outv]',
48
+ '-map', '[outa]',
49
+ '-c:v', 'libx264',
50
+ '-preset', 'ultrafast',
51
+ '-crf', '23',
52
+ '-threads', '4',
53
+ '-c:a', 'aac',
54
+ '-b:a', '128k',
55
+ outputPath,
56
+ ];
57
+ logger.info(`[SinglePassEdit] Editing ${keepSegments.length} segments → ${outputPath}`);
58
+ return new Promise((resolve, reject) => {
59
+ execFile(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {
60
+ if (error) {
61
+ logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`);
62
+ reject(new Error(`Single-pass edit failed: ${error.message}`));
63
+ return;
64
+ }
65
+ logger.info(`[SinglePassEdit] Complete: ${outputPath}`);
66
+ resolve(outputPath);
67
+ });
68
+ });
69
+ }
70
+ /**
71
+ * Single-pass silence removal + caption burning using FFmpeg filter_complex.
72
+ * Uses trim+setpts+concat for frame-accurate cuts, then chains ass filter for captions.
73
+ * One re-encode, perfect timestamp alignment.
74
+ */
75
+ export async function singlePassEditAndCaption(inputPath, keepSegments, assPath, outputPath) {
76
+ // Copy ASS + bundled fonts to temp dir to avoid Windows drive colon issue
77
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'caption-'));
78
+ const tempAss = path.join(tempDir, 'captions.ass');
79
+ await fs.copyFile(assPath, tempAss);
80
+ const fontFiles = await fs.readdir(FONTS_DIR);
81
+ for (const f of fontFiles) {
82
+ if (f.endsWith('.ttf') || f.endsWith('.otf')) {
83
+ await fs.copyFile(path.join(FONTS_DIR, f), path.join(tempDir, f));
84
+ }
85
+ }
86
+ const filterComplex = buildFilterComplex(keepSegments, {
87
+ assFilename: 'captions.ass',
88
+ fontsdir: '.',
89
+ });
90
+ const args = [
91
+ '-y',
92
+ '-i', inputPath,
93
+ '-filter_complex', filterComplex,
94
+ '-map', '[outv]',
95
+ '-map', '[ca]',
96
+ '-c:v', 'libx264',
97
+ '-preset', 'ultrafast',
98
+ '-crf', '23',
99
+ '-threads', '4',
100
+ '-c:a', 'aac',
101
+ '-b:a', '128k',
102
+ outputPath,
103
+ ];
104
+ logger.info(`[SinglePassEdit] Processing ${keepSegments.length} segments with captions → ${outputPath}`);
105
+ return new Promise((resolve, reject) => {
106
+ execFile(ffmpegPath, args, { cwd: tempDir, maxBuffer: 50 * 1024 * 1024 }, async (error, _stdout, stderr) => {
107
+ // Cleanup temp
108
+ const files = await fs.readdir(tempDir).catch(() => []);
109
+ for (const f of files) {
110
+ await fs.unlink(path.join(tempDir, f)).catch(() => { });
111
+ }
112
+ await fs.rmdir(tempDir).catch(() => { });
113
+ if (error) {
114
+ logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`);
115
+ reject(new Error(`Single-pass edit failed: ${error.message}`));
116
+ return;
117
+ }
118
+ logger.info(`[SinglePassEdit] Complete: ${outputPath}`);
119
+ resolve(outputPath);
120
+ });
121
+ });
122
+ }
123
+ //# sourceMappingURL=singlePassEdit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singlePassEdit.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/singlePassEdit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface SearchResult {
2
+ title: string;
3
+ url: string;
4
+ snippet: string;
5
+ }
6
+ export declare function searchWeb(query: string, numResults?: number): Promise<SearchResult[]>;
7
+ export declare function searchTopics(topics: string[]): Promise<Map<string, SearchResult[]>>;
8
+ //# sourceMappingURL=exaClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exaClient.d.ts","sourceRoot":"","sources":["../../../src/tools/search/exaClient.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAyB9F;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAOzF"}
@@ -0,0 +1,38 @@
1
+ // Exa AI web search client
2
+ // Searches the web for relevant links based on topics
3
+ import Exa from 'exa-js';
4
+ import { getConfig } from '../../config/environment';
5
+ import logger from '../../config/logger';
6
+ export async function searchWeb(query, numResults = 5) {
7
+ const config = getConfig();
8
+ if (!config.EXA_API_KEY) {
9
+ logger.warn('EXA_API_KEY not set — skipping web search');
10
+ return [];
11
+ }
12
+ const exa = new Exa(config.EXA_API_KEY);
13
+ try {
14
+ const results = await exa.searchAndContents(query, {
15
+ numResults,
16
+ text: { maxCharacters: 200 }
17
+ });
18
+ return results.results.map(r => ({
19
+ title: r.title || '',
20
+ url: r.url,
21
+ // Exa SDK searchAndContents returns `text` when text option is used, but the type doesn't include it
22
+ snippet: r.text || ''
23
+ }));
24
+ }
25
+ catch (err) {
26
+ logger.error(`Exa search failed: ${err instanceof Error ? err.message : err}`);
27
+ return [];
28
+ }
29
+ }
30
+ export async function searchTopics(topics) {
31
+ const resultsMap = new Map();
32
+ for (const topic of topics) {
33
+ const results = await searchWeb(topic, 3);
34
+ resultsMap.set(topic, results);
35
+ }
36
+ return resultsMap;
37
+ }
38
+ //# sourceMappingURL=exaClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exaClient.js","sourceRoot":"","sources":["../../../src/tools/search/exaClient.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,sDAAsD;AAEtD,OAAO,GAAG,MAAM,QAAQ,CAAA;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,MAAM,MAAM,qBAAqB,CAAA;AAQxC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAa,EAAE,aAAqB,CAAC;IACnE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAA;QACxD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAEvC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,iBAAiB,CAAC,KAAK,EAAE;YACjD,UAAU;YACV,IAAI,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE;SAC7B,CAAC,CAAA;QAEF,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/B,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;YACpB,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,qGAAqG;YACrG,OAAO,EAAG,CAAkC,CAAC,IAAI,IAAI,EAAE;SACxD,CAAC,CAAC,CAAA;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAA;QAC9E,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAgB;IACjD,MAAM,UAAU,GAAG,IAAI,GAAG,EAA0B,CAAA;IACpD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QACzC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Transcript } from '../../types';
2
+ export declare function transcribeAudio(audioPath: string): Promise<Transcript>;
3
+ //# sourceMappingURL=whisperClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisperClient.d.ts","sourceRoot":"","sources":["../../../src/tools/whisper/whisperClient.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAiB,MAAM,aAAa,CAAA;AAKvD,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsF5E"}
@@ -0,0 +1,77 @@
1
+ import OpenAI from 'openai';
2
+ import fs from 'fs';
3
+ import { getConfig } from '../../config/environment';
4
+ import logger from '../../config/logger';
5
+ import { getWhisperPrompt } from '../../config/brand';
6
+ const MAX_FILE_SIZE_MB = 25;
7
+ const WARN_FILE_SIZE_MB = 20;
8
+ export async function transcribeAudio(audioPath) {
9
+ logger.info(`Starting Whisper transcription: ${audioPath}`);
10
+ if (!fs.existsSync(audioPath)) {
11
+ throw new Error(`Audio file not found: ${audioPath}`);
12
+ }
13
+ // Check file size against Whisper's 25MB limit
14
+ const stats = fs.statSync(audioPath);
15
+ const fileSizeMB = stats.size / (1024 * 1024);
16
+ if (fileSizeMB > MAX_FILE_SIZE_MB) {
17
+ throw new Error(`Audio file exceeds Whisper's 25MB limit (${fileSizeMB.toFixed(1)}MB). ` +
18
+ 'The file should be split into smaller chunks before transcription.');
19
+ }
20
+ if (fileSizeMB > WARN_FILE_SIZE_MB) {
21
+ logger.warn(`Audio file is ${fileSizeMB.toFixed(1)}MB — approaching 25MB limit`);
22
+ }
23
+ const config = getConfig();
24
+ const openai = new OpenAI({ apiKey: config.OPENAI_API_KEY });
25
+ try {
26
+ const prompt = getWhisperPrompt();
27
+ const response = await openai.audio.transcriptions.create({
28
+ model: 'whisper-1',
29
+ file: fs.createReadStream(audioPath),
30
+ response_format: 'verbose_json',
31
+ timestamp_granularities: ['word', 'segment'],
32
+ ...(prompt && { prompt }),
33
+ });
34
+ // The verbose_json response includes segments and words at the top level,
35
+ // but the OpenAI SDK types don't expose them — cast to access raw fields.
36
+ const verboseResponse = response;
37
+ const rawSegments = (verboseResponse.segments ?? []);
38
+ const rawWords = (verboseResponse.words ?? []);
39
+ const words = rawWords.map((w) => ({
40
+ word: w.word,
41
+ start: w.start,
42
+ end: w.end,
43
+ }));
44
+ const segments = rawSegments.map((s) => ({
45
+ id: s.id,
46
+ text: s.text.trim(),
47
+ start: s.start,
48
+ end: s.end,
49
+ words: rawWords
50
+ .filter((w) => w.start >= s.start && w.end <= s.end)
51
+ .map((w) => ({ word: w.word, start: w.start, end: w.end })),
52
+ }));
53
+ logger.info(`Transcription complete — ${segments.length} segments, ` +
54
+ `${words.length} words, language=${response.language}`);
55
+ return {
56
+ text: response.text,
57
+ segments,
58
+ words,
59
+ language: response.language ?? 'unknown',
60
+ duration: response.duration ?? 0,
61
+ };
62
+ }
63
+ catch (error) {
64
+ const message = error instanceof Error ? error.message : String(error);
65
+ logger.error(`Whisper transcription failed: ${message}`);
66
+ // OpenAI SDK errors expose a `status` property for HTTP status codes
67
+ const status = error.status;
68
+ if (status === 401) {
69
+ throw new Error('OpenAI API authentication failed. Check your OPENAI_API_KEY.');
70
+ }
71
+ if (status === 429) {
72
+ throw new Error('OpenAI API rate limit exceeded. Please try again later.');
73
+ }
74
+ throw new Error(`Whisper transcription failed: ${message}`);
75
+ }
76
+ }
77
+ //# sourceMappingURL=whisperClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisperClient.js","sourceRoot":"","sources":["../../../src/tools/whisper/whisperClient.ts"],"names":[],"mappings":"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"}