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,84 @@
1
+ /**
2
+ * Caption generator for the Advanced SubStation Alpha (ASS) subtitle format.
3
+ *
4
+ * ### Why ASS instead of SRT/VTT?
5
+ * ASS supports inline style overrides — font size, color, and animation per
6
+ * character/word — which enables the "active word pop" karaoke effect used
7
+ * in modern short-form video (TikTok, Reels). SRT and VTT only support
8
+ * plain text or basic HTML tags with no per-word timing control.
9
+ *
10
+ * ### Karaoke word highlighting approach
11
+ * Instead of ASS's native `\k` karaoke tags (which highlight left-to-right
12
+ * within a line), we generate **one Dialogue line per word-state**. Each line
13
+ * renders the entire caption group but with the currently-spoken word in a
14
+ * different color and size. Contiguous end/start timestamps between
15
+ * word-states prevent flicker. This gives us full control over the visual
16
+ * treatment (color, font-size, scale animations) without the limitations
17
+ * of the `\k` tag.
18
+ *
19
+ * @module captionGenerator
20
+ */
21
+ import { Transcript, CaptionStyle } from '../../types';
22
+ export declare function generateSRT(transcript: Transcript): string;
23
+ export declare function generateVTT(transcript: Transcript): string;
24
+ /**
25
+ * Generate premium ASS captions with active-word-pop highlighting.
26
+ *
27
+ * ### How it works
28
+ * Words are grouped by speech bursts (split on silence gaps > 0.8s or after
29
+ * 8 words). Within each group, one Dialogue line is emitted per word — the
30
+ * full group is shown each time, but the "active" word gets a different color
31
+ * and larger font size. This creates a karaoke-style bounce effect.
32
+ *
33
+ * @param transcript - Full transcript with word-level timestamps
34
+ * @param style - Visual style: 'shorts' (large centered), 'medium' (small bottom),
35
+ * or 'portrait' (Opus Clips style with green highlight + scale animation)
36
+ * @returns Complete ASS file content (header + dialogue lines)
37
+ */
38
+ export declare function generateStyledASS(transcript: Transcript, style?: CaptionStyle): string;
39
+ /**
40
+ * Generate premium ASS captions for a single contiguous segment.
41
+ * Filters words within [startTime, endTime] (plus buffer), adjusts timestamps
42
+ * relative to the clip's buffered start so they align with the extracted video.
43
+ */
44
+ export declare function generateStyledASSForSegment(transcript: Transcript, startTime: number, endTime: number, buffer?: number, style?: CaptionStyle): string;
45
+ /**
46
+ * Generate premium ASS captions for a composite clip made of multiple segments.
47
+ * Each segment's words are extracted and remapped to the concatenated timeline,
48
+ * accounting for the buffer added during clip extraction.
49
+ */
50
+ export declare function generateStyledASSForComposite(transcript: Transcript, segments: {
51
+ start: number;
52
+ end: number;
53
+ }[], buffer?: number, style?: CaptionStyle): string;
54
+ /**
55
+ * Generate ASS dialogue lines for a hook text overlay at the top of the video.
56
+ *
57
+ * The hook is a short attention-grabbing phrase (e.g. "Here's why you should
58
+ * learn TypeScript") displayed as a translucent pill/badge at the top of a
59
+ * portrait video for the first few seconds.
60
+ *
61
+ * Uses the `Hook` style defined in {@link ASS_HEADER_PORTRAIT} which has
62
+ * `BorderStyle: 3` (opaque box background) and `Alignment: 8` (top-center).
63
+ *
64
+ * The `\fad(300,500)` tag creates a 300ms fade-in and 500ms fade-out so the
65
+ * hook doesn't appear/disappear abruptly.
66
+ *
67
+ * @param hookText - The attention-grabbing phrase (truncated to 60 chars)
68
+ * @param displayDuration - How long to show the hook in seconds (default: 4s)
69
+ * @param _style - Caption style (currently only 'portrait' uses hooks)
70
+ * @returns A single ASS Dialogue line to append to the Events section
71
+ */
72
+ export declare function generateHookOverlay(hookText: string, displayDuration?: number, _style?: CaptionStyle): string;
73
+ /**
74
+ * Generate a complete portrait ASS file with captions AND hook text overlay.
75
+ */
76
+ export declare function generatePortraitASSWithHook(transcript: Transcript, hookText: string, startTime: number, endTime: number, buffer?: number): string;
77
+ /**
78
+ * Generate a complete portrait ASS file for a composite clip with captions AND hook text overlay.
79
+ */
80
+ export declare function generatePortraitASSWithHookComposite(transcript: Transcript, segments: {
81
+ start: number;
82
+ end: number;
83
+ }[], hookText: string, buffer?: number): string;
84
+ //# sourceMappingURL=captionGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captionGenerator.d.ts","sourceRoot":"","sources":["../../../src/tools/captions/captionGenerator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,UAAU,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAA;AA+ErE,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAW1D;AAMD,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAW1D;AA6LD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,GAAE,YAAuB,GAAG,MAAM,CAMhG;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAY,EACpB,KAAK,GAAE,YAAuB,GAC7B,MAAM,CAiBR;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1C,MAAM,GAAE,MAAY,EACpB,KAAK,GAAE,YAAuB,GAC7B,MAAM,CA4BR;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,eAAe,GAAE,MAAY,EAC7B,MAAM,GAAE,YAAyB,GAChC,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,oCAAoC,CAClD,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1C,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAIR"}
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Caption generator for the Advanced SubStation Alpha (ASS) subtitle format.
3
+ *
4
+ * ### Why ASS instead of SRT/VTT?
5
+ * ASS supports inline style overrides — font size, color, and animation per
6
+ * character/word — which enables the "active word pop" karaoke effect used
7
+ * in modern short-form video (TikTok, Reels). SRT and VTT only support
8
+ * plain text or basic HTML tags with no per-word timing control.
9
+ *
10
+ * ### Karaoke word highlighting approach
11
+ * Instead of ASS's native `\k` karaoke tags (which highlight left-to-right
12
+ * within a line), we generate **one Dialogue line per word-state**. Each line
13
+ * renders the entire caption group but with the currently-spoken word in a
14
+ * different color and size. Contiguous end/start timestamps between
15
+ * word-states prevent flicker. This gives us full control over the visual
16
+ * treatment (color, font-size, scale animations) without the limitations
17
+ * of the `\k` tag.
18
+ *
19
+ * @module captionGenerator
20
+ */
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers
23
+ // ---------------------------------------------------------------------------
24
+ /** Pad a number to a fixed width with leading zeros. */
25
+ function pad(n, width) {
26
+ return String(n).padStart(width, '0');
27
+ }
28
+ /** Convert seconds → SRT timestamp "HH:MM:SS,mmm" */
29
+ function toSRT(seconds) {
30
+ const h = Math.floor(seconds / 3600);
31
+ const m = Math.floor((seconds % 3600) / 60);
32
+ const s = Math.floor(seconds % 60);
33
+ const ms = Math.round((seconds - Math.floor(seconds)) * 1000);
34
+ return `${pad(h, 2)}:${pad(m, 2)}:${pad(s, 2)},${pad(ms, 3)}`;
35
+ }
36
+ /** Convert seconds → VTT timestamp "HH:MM:SS.mmm" */
37
+ function toVTT(seconds) {
38
+ return toSRT(seconds).replace(',', '.');
39
+ }
40
+ /** Convert seconds → ASS timestamp "H:MM:SS.cc" */
41
+ function toASS(seconds) {
42
+ const h = Math.floor(seconds / 3600);
43
+ const m = Math.floor((seconds % 3600) / 60);
44
+ const s = Math.floor(seconds % 60);
45
+ const cs = Math.round((seconds - Math.floor(seconds)) * 100);
46
+ return `${h}:${pad(m, 2)}:${pad(s, 2)}.${pad(cs, 2)}`;
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Premium caption constants
50
+ // ---------------------------------------------------------------------------
51
+ /** Silence gap threshold in seconds – gaps longer than this split caption groups. */
52
+ const SILENCE_GAP_THRESHOLD = 0.8;
53
+ /** Maximum words displayed simultaneously in a caption group. */
54
+ const MAX_WORDS_PER_GROUP = 8;
55
+ /** Target words per display line within a group (splits into 2 lines above this). */
56
+ const WORDS_PER_LINE = 4;
57
+ /** ASS BGR color for the active (currently-spoken) word – yellow. */
58
+ const ACTIVE_COLOR = '\\c&H00FFFF&';
59
+ /** ASS BGR color for inactive words – white. */
60
+ const BASE_COLOR = '\\c&HFFFFFF&';
61
+ /** Font size for the active word. */
62
+ const ACTIVE_FONT_SIZE = 54;
63
+ /** Font size for inactive words (matches style default). */
64
+ const BASE_FONT_SIZE = 42;
65
+ // ---------------------------------------------------------------------------
66
+ // Medium caption constants (smaller, bottom-positioned for longer content)
67
+ // ---------------------------------------------------------------------------
68
+ /** Font size for the active word in medium style. */
69
+ const MEDIUM_ACTIVE_FONT_SIZE = 40;
70
+ /** Font size for inactive words in medium style. */
71
+ const MEDIUM_BASE_FONT_SIZE = 32;
72
+ // ---------------------------------------------------------------------------
73
+ // Portrait caption constants (Opus Clips style)
74
+ // ---------------------------------------------------------------------------
75
+ /** Font size for the active word in portrait style. */
76
+ const PORTRAIT_ACTIVE_FONT_SIZE = 78;
77
+ /** Font size for inactive words in portrait style. */
78
+ const PORTRAIT_BASE_FONT_SIZE = 66;
79
+ /** ASS BGR color for the active word in portrait style – green. */
80
+ const PORTRAIT_ACTIVE_COLOR = '\\c&H00FF00&';
81
+ /** ASS BGR color for inactive words in portrait style – white. */
82
+ const PORTRAIT_BASE_COLOR = '\\c&HFFFFFF&';
83
+ // ---------------------------------------------------------------------------
84
+ // SRT (segment-level)
85
+ // ---------------------------------------------------------------------------
86
+ export function generateSRT(transcript) {
87
+ return transcript.segments
88
+ .map((seg, i) => {
89
+ const idx = i + 1;
90
+ const start = toSRT(seg.start);
91
+ const end = toSRT(seg.end);
92
+ const text = seg.text.trim();
93
+ return `${idx}\n${start} --> ${end}\n${text}`;
94
+ })
95
+ .join('\n\n')
96
+ .concat('\n');
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // VTT (segment-level)
100
+ // ---------------------------------------------------------------------------
101
+ export function generateVTT(transcript) {
102
+ const cues = transcript.segments
103
+ .map((seg) => {
104
+ const start = toVTT(seg.start);
105
+ const end = toVTT(seg.end);
106
+ const text = seg.text.trim();
107
+ return `${start} --> ${end}\n${text}`;
108
+ })
109
+ .join('\n\n');
110
+ return `WEBVTT\n\n${cues}\n`;
111
+ }
112
+ // ---------------------------------------------------------------------------
113
+ // ASS – Premium active-word-pop captions
114
+ // ---------------------------------------------------------------------------
115
+ /**
116
+ * ASS header for landscape (16:9, 1920×1080) captions.
117
+ *
118
+ * ### Style fields explained (comma-separated in the Style line):
119
+ * - `Fontname: Montserrat` — bundled with the project; FFmpeg's `ass` filter
120
+ * uses `fontsdir=.` so libass finds the .ttf files next to the .ass file.
121
+ * - `Fontsize: 42` — base size for inactive words
122
+ * - `PrimaryColour: &H00FFFFFF` — white (ASS uses `&HAABBGGRR` — alpha, blue, green, red)
123
+ * - `OutlineColour: &H00000000` — black outline for readability on any background
124
+ * - `BackColour: &H80000000` — 50% transparent black shadow
125
+ * - `Bold: 1` — bold for better readability at small sizes
126
+ * - `BorderStyle: 1` — outline + drop shadow (not opaque box)
127
+ * - `Outline: 3` — 3px outline thickness
128
+ * - `Shadow: 1` — 1px drop shadow
129
+ * - `Alignment: 2` — bottom-center (SSA alignment: 1=left, 2=center, 3=right;
130
+ * add 4 for top, 8 for middle — so 2 = bottom-center)
131
+ * - `MarginV: 40` — 40px above the bottom edge
132
+ * - `WrapStyle: 0` — smart word wrap
133
+ */
134
+ const ASS_HEADER = `[Script Info]
135
+ Title: Auto-generated captions
136
+ ScriptType: v4.00+
137
+ PlayResX: 1920
138
+ PlayResY: 1080
139
+ WrapStyle: 0
140
+
141
+ [V4+ Styles]
142
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
143
+ Style: Default,Montserrat,42,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,20,20,40,1
144
+
145
+ [Events]
146
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
147
+ `;
148
+ /**
149
+ * ASS header for portrait (9:16, 1080×1920) captions — used for shorts.
150
+ *
151
+ * Key differences from the landscape header:
152
+ * - `PlayResX/Y: 1080×1920` — matches portrait video dimensions
153
+ * - `Fontsize: 78` — larger base font for vertical video viewing (small screens)
154
+ * - `MarginV: 700` — pushes captions to the center-bottom area, leaving room
155
+ * for the hook overlay at the top
156
+ * - Includes a `Hook` style: semi-transparent pill/badge background
157
+ * (`BorderStyle: 3` = opaque box) for the opening hook text overlay
158
+ */
159
+ const ASS_HEADER_PORTRAIT = `[Script Info]
160
+ Title: Auto-generated captions
161
+ ScriptType: v4.00+
162
+ PlayResX: 1080
163
+ PlayResY: 1920
164
+ WrapStyle: 0
165
+
166
+ [V4+ Styles]
167
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
168
+ Style: Default,Montserrat,78,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,30,30,700,1
169
+ Style: Hook,Montserrat,56,&H00333333,&H00333333,&H60D0D0D0,&H60E0E0E0,1,0,0,0,100,100,2,0,3,18,2,8,80,80,60,1
170
+
171
+ [Events]
172
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
173
+ `;
174
+ /**
175
+ * ASS header for medium-style captions (1920×1080 but smaller font).
176
+ *
177
+ * Used for longer clips where large captions would be distracting.
178
+ * - `Fontsize: 32` — smaller than the shorts style
179
+ * - `Alignment: 2` — bottom-center
180
+ * - `MarginV: 60` — slightly higher from the bottom edge to avoid UI overlaps
181
+ */
182
+ const ASS_HEADER_MEDIUM = `[Script Info]
183
+ Title: Auto-generated captions
184
+ ScriptType: v4.00+
185
+ PlayResX: 1920
186
+ PlayResY: 1080
187
+ WrapStyle: 0
188
+
189
+ [V4+ Styles]
190
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
191
+ Style: Default,Montserrat,32,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,2,1,2,20,20,60,1
192
+
193
+ [Events]
194
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
195
+ `;
196
+ /**
197
+ * Group words into caption groups split on silence gaps and max word count.
198
+ * All words within a group are displayed simultaneously; captions disappear
199
+ * entirely during gaps longer than SILENCE_GAP_THRESHOLD.
200
+ */
201
+ function groupWordsBySpeech(words) {
202
+ if (words.length === 0)
203
+ return [];
204
+ const groups = [];
205
+ let current = [];
206
+ for (let i = 0; i < words.length; i++) {
207
+ current.push(words[i]);
208
+ const isLast = i === words.length - 1;
209
+ const hasGap = !isLast && words[i + 1].start - words[i].end > SILENCE_GAP_THRESHOLD;
210
+ const atMax = current.length >= MAX_WORDS_PER_GROUP;
211
+ if (isLast || hasGap || atMax) {
212
+ groups.push(current);
213
+ current = [];
214
+ }
215
+ }
216
+ return groups;
217
+ }
218
+ /**
219
+ * Split a caption group into 1–2 display lines.
220
+ * Groups with ≤ WORDS_PER_LINE words stay on one line; larger groups
221
+ * are split at the midpoint into two lines joined with \\N.
222
+ */
223
+ function splitGroupIntoLines(group) {
224
+ if (group.length <= WORDS_PER_LINE)
225
+ return [group];
226
+ const mid = Math.ceil(group.length / 2);
227
+ return [group.slice(0, mid), group.slice(mid)];
228
+ }
229
+ /**
230
+ * Build premium ASS dialogue lines with active-word highlighting.
231
+ * Generates one Dialogue line per word-state: the full caption group is
232
+ * rendered with the currently-spoken word in yellow at a larger size while
233
+ * all other words stay white at the base size. Contiguous end/start times
234
+ * between word-states prevent flicker.
235
+ */
236
+ function buildPremiumDialogueLines(words, style = 'shorts') {
237
+ const activeFontSize = style === 'portrait' ? PORTRAIT_ACTIVE_FONT_SIZE
238
+ : style === 'medium' ? MEDIUM_ACTIVE_FONT_SIZE : ACTIVE_FONT_SIZE;
239
+ const baseFontSize = style === 'portrait' ? PORTRAIT_BASE_FONT_SIZE
240
+ : style === 'medium' ? MEDIUM_BASE_FONT_SIZE : BASE_FONT_SIZE;
241
+ const groups = groupWordsBySpeech(words);
242
+ const dialogues = [];
243
+ for (const group of groups) {
244
+ const displayLines = splitGroupIntoLines(group);
245
+ for (let activeIdx = 0; activeIdx < group.length; activeIdx++) {
246
+ const activeWord = group[activeIdx];
247
+ // Contiguous timing: end = next word's start, or this word's own end
248
+ const endTime = activeIdx < group.length - 1
249
+ ? group[activeIdx + 1].start
250
+ : activeWord.end;
251
+ // Render all words across display lines with the active word highlighted
252
+ const renderedLines = [];
253
+ let globalIdx = 0;
254
+ for (const line of displayLines) {
255
+ const rendered = line.map((w) => {
256
+ const idx = globalIdx++;
257
+ const text = w.word.trim();
258
+ if (idx === activeIdx) {
259
+ if (style === 'portrait') {
260
+ // Opus Clips style: green color + scale pop animation
261
+ return `{${PORTRAIT_ACTIVE_COLOR}\\fs${activeFontSize}\\fscx130\\fscy130\\t(0,150,\\fscx100\\fscy100)}${text}`;
262
+ }
263
+ return `{${ACTIVE_COLOR}\\fs${activeFontSize}}${text}`;
264
+ }
265
+ if (style === 'portrait') {
266
+ return `{${PORTRAIT_BASE_COLOR}\\fs${baseFontSize}}${text}`;
267
+ }
268
+ return `{${BASE_COLOR}\\fs${baseFontSize}}${text}`;
269
+ });
270
+ renderedLines.push(rendered.join(' '));
271
+ }
272
+ const text = renderedLines.join('\\N');
273
+ dialogues.push(`Dialogue: 0,${toASS(activeWord.start)},${toASS(endTime)},Default,,0,0,0,,${text}`);
274
+ }
275
+ }
276
+ return dialogues;
277
+ }
278
+ /**
279
+ * Generate premium ASS captions with active-word-pop highlighting.
280
+ *
281
+ * ### How it works
282
+ * Words are grouped by speech bursts (split on silence gaps > 0.8s or after
283
+ * 8 words). Within each group, one Dialogue line is emitted per word — the
284
+ * full group is shown each time, but the "active" word gets a different color
285
+ * and larger font size. This creates a karaoke-style bounce effect.
286
+ *
287
+ * @param transcript - Full transcript with word-level timestamps
288
+ * @param style - Visual style: 'shorts' (large centered), 'medium' (small bottom),
289
+ * or 'portrait' (Opus Clips style with green highlight + scale animation)
290
+ * @returns Complete ASS file content (header + dialogue lines)
291
+ */
292
+ export function generateStyledASS(transcript, style = 'shorts') {
293
+ const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER;
294
+ const allWords = transcript.words;
295
+ if (allWords.length === 0)
296
+ return header;
297
+ return header + buildPremiumDialogueLines(allWords, style).join('\n') + '\n';
298
+ }
299
+ /**
300
+ * Generate premium ASS captions for a single contiguous segment.
301
+ * Filters words within [startTime, endTime] (plus buffer), adjusts timestamps
302
+ * relative to the clip's buffered start so they align with the extracted video.
303
+ */
304
+ export function generateStyledASSForSegment(transcript, startTime, endTime, buffer = 1.0, style = 'shorts') {
305
+ const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER;
306
+ const bufferedStart = Math.max(0, startTime - buffer);
307
+ const bufferedEnd = endTime + buffer;
308
+ const words = transcript.words.filter((w) => w.start >= bufferedStart && w.end <= bufferedEnd);
309
+ if (words.length === 0)
310
+ return header;
311
+ const adjusted = words.map((w) => ({
312
+ word: w.word,
313
+ start: w.start - bufferedStart,
314
+ end: w.end - bufferedStart,
315
+ }));
316
+ return header + buildPremiumDialogueLines(adjusted, style).join('\n') + '\n';
317
+ }
318
+ /**
319
+ * Generate premium ASS captions for a composite clip made of multiple segments.
320
+ * Each segment's words are extracted and remapped to the concatenated timeline,
321
+ * accounting for the buffer added during clip extraction.
322
+ */
323
+ export function generateStyledASSForComposite(transcript, segments, buffer = 1.0, style = 'shorts') {
324
+ const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER;
325
+ const allAdjusted = [];
326
+ let runningOffset = 0;
327
+ for (const seg of segments) {
328
+ const bufferedStart = Math.max(0, seg.start - buffer);
329
+ const bufferedEnd = seg.end + buffer;
330
+ const segDuration = bufferedEnd - bufferedStart;
331
+ const words = transcript.words.filter((w) => w.start >= bufferedStart && w.end <= bufferedEnd);
332
+ for (const w of words) {
333
+ allAdjusted.push({
334
+ word: w.word,
335
+ start: w.start - bufferedStart + runningOffset,
336
+ end: w.end - bufferedStart + runningOffset,
337
+ });
338
+ }
339
+ runningOffset += segDuration;
340
+ }
341
+ if (allAdjusted.length === 0)
342
+ return header;
343
+ return header + buildPremiumDialogueLines(allAdjusted, style).join('\n') + '\n';
344
+ }
345
+ // ---------------------------------------------------------------------------
346
+ // Hook text overlay for portrait shorts
347
+ // ---------------------------------------------------------------------------
348
+ /** Maximum characters for hook text before truncation. */
349
+ const HOOK_TEXT_MAX_LENGTH = 60;
350
+ /**
351
+ * Generate ASS dialogue lines for a hook text overlay at the top of the video.
352
+ *
353
+ * The hook is a short attention-grabbing phrase (e.g. "Here's why you should
354
+ * learn TypeScript") displayed as a translucent pill/badge at the top of a
355
+ * portrait video for the first few seconds.
356
+ *
357
+ * Uses the `Hook` style defined in {@link ASS_HEADER_PORTRAIT} which has
358
+ * `BorderStyle: 3` (opaque box background) and `Alignment: 8` (top-center).
359
+ *
360
+ * The `\fad(300,500)` tag creates a 300ms fade-in and 500ms fade-out so the
361
+ * hook doesn't appear/disappear abruptly.
362
+ *
363
+ * @param hookText - The attention-grabbing phrase (truncated to 60 chars)
364
+ * @param displayDuration - How long to show the hook in seconds (default: 4s)
365
+ * @param _style - Caption style (currently only 'portrait' uses hooks)
366
+ * @returns A single ASS Dialogue line to append to the Events section
367
+ */
368
+ export function generateHookOverlay(hookText, displayDuration = 4.0, _style = 'portrait') {
369
+ const text = hookText.length > HOOK_TEXT_MAX_LENGTH
370
+ ? hookText.slice(0, HOOK_TEXT_MAX_LENGTH - 3) + '...'
371
+ : hookText;
372
+ return `Dialogue: 1,${toASS(0)},${toASS(displayDuration)},Hook,,0,0,0,,{\\fad(300,500)}${text}`;
373
+ }
374
+ /**
375
+ * Generate a complete portrait ASS file with captions AND hook text overlay.
376
+ */
377
+ export function generatePortraitASSWithHook(transcript, hookText, startTime, endTime, buffer) {
378
+ const baseASS = generateStyledASSForSegment(transcript, startTime, endTime, buffer, 'portrait');
379
+ const hookLine = generateHookOverlay(hookText, 4.0, 'portrait');
380
+ return baseASS + hookLine + '\n';
381
+ }
382
+ /**
383
+ * Generate a complete portrait ASS file for a composite clip with captions AND hook text overlay.
384
+ */
385
+ export function generatePortraitASSWithHookComposite(transcript, segments, hookText, buffer) {
386
+ const baseASS = generateStyledASSForComposite(transcript, segments, buffer, 'portrait');
387
+ const hookLine = generateHookOverlay(hookText, 4.0, 'portrait');
388
+ return baseASS + hookLine + '\n';
389
+ }
390
+ //# sourceMappingURL=captionGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captionGenerator.js","sourceRoot":"","sources":["../../../src/tools/captions/captionGenerator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Supported output aspect ratios.
3
+ * - `16:9` — standard landscape (YouTube, desktop)
4
+ * - `9:16` — portrait / vertical (TikTok, Reels, Shorts)
5
+ * - `1:1` — square (LinkedIn, Twitter)
6
+ * - `4:5` — tall feed (Instagram feed)
7
+ */
8
+ export type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5';
9
+ /** Social-media platforms we generate video variants for. */
10
+ export type Platform = 'tiktok' | 'youtube-shorts' | 'instagram-reels' | 'instagram-feed' | 'linkedin' | 'youtube' | 'twitter';
11
+ /**
12
+ * Maps each platform to its preferred aspect ratio.
13
+ * Multiple platforms may share a ratio (e.g. TikTok + Reels both use 9:16),
14
+ * which lets {@link generatePlatformVariants} deduplicate encodes.
15
+ */
16
+ export declare const PLATFORM_RATIOS: Record<Platform, AspectRatio>;
17
+ /**
18
+ * Canonical pixel dimensions for each aspect ratio.
19
+ * Width is always 1080 px for non-landscape ratios (the standard vertical
20
+ * video width); landscape stays at 1920×1080 for full HD.
21
+ */
22
+ export declare const DIMENSIONS: Record<AspectRatio, {
23
+ width: number;
24
+ height: number;
25
+ }>;
26
+ export interface ConvertOptions {
27
+ /** Fallback to letterbox/pillarbox instead of cropping (default: false) */
28
+ letterbox?: boolean;
29
+ }
30
+ /**
31
+ * Convert a video's aspect ratio using FFmpeg center-crop.
32
+ *
33
+ * - 16:9 → 9:16: crops the center column to portrait
34
+ * - 16:9 → 1:1: crops to a center square
35
+ * - Same ratio: stream-copies without re-encoding
36
+ *
37
+ * @returns The output path on success
38
+ */
39
+ export declare function convertAspectRatio(inputPath: string, outputPath: string, targetRatio: AspectRatio, options?: ConvertOptions): Promise<string>;
40
+ /**
41
+ * Smart portrait (9:16) conversion → 1080×1920.
42
+ *
43
+ * Screen panel: 1080×1248 (65%), Webcam panel: 1080×672 (35%).
44
+ * Total: 1080×1920 — standard TikTok / Reels / Shorts dimensions.
45
+ *
46
+ * Falls back to center-crop 9:16 if no webcam is detected.
47
+ *
48
+ * @param inputPath - Source landscape video
49
+ * @param outputPath - Destination path for the portrait video
50
+ */
51
+ export declare function convertToPortraitSmart(inputPath: string, outputPath: string): Promise<string>;
52
+ /**
53
+ * Smart square (1:1) conversion → 1080×1080.
54
+ *
55
+ * Screen panel: 1080×700 (65%), Webcam panel: 1080×380 (35%).
56
+ * Total: 1080×1080 — standard LinkedIn / Twitter square format.
57
+ *
58
+ * Falls back to center-crop 1:1 if no webcam is detected.
59
+ *
60
+ * @param inputPath - Source landscape video
61
+ * @param outputPath - Destination path for the square video
62
+ */
63
+ export declare function convertToSquareSmart(inputPath: string, outputPath: string): Promise<string>;
64
+ /**
65
+ * Smart feed (4:5) conversion → 1080×1350.
66
+ *
67
+ * Screen panel: 1080×878 (65%), Webcam panel: 1080×472 (35%).
68
+ * Total: 1080×1350 — Instagram feed's preferred tall format.
69
+ *
70
+ * Falls back to center-crop 4:5 if no webcam is detected.
71
+ *
72
+ * @param inputPath - Source landscape video
73
+ * @param outputPath - Destination path for the 4:5 video
74
+ */
75
+ export declare function convertToFeedSmart(inputPath: string, outputPath: string): Promise<string>;
76
+ /**
77
+ * Generate platform-specific aspect-ratio variants of a short clip.
78
+ *
79
+ * ### Routing logic
80
+ * 1. Maps each requested platform to its aspect ratio via {@link PLATFORM_RATIOS}.
81
+ * 2. **Deduplicates by ratio** — if TikTok and Reels both need 9:16, only one
82
+ * encode is performed and both platforms reference the same output file.
83
+ * 3. Skips 16:9 entirely since the source is already landscape.
84
+ * 4. Routes each ratio to its smart converter (portrait / square / feed) for
85
+ * split-screen layout, falling back to {@link convertAspectRatio} for any
86
+ * ratio without a smart converter.
87
+ *
88
+ * @param inputPath - Source video (16:9 landscape)
89
+ * @param outputDir - Directory to write variant files into
90
+ * @param slug - Base filename slug (e.g. "my-video-short-1")
91
+ * @param platforms - Platforms to generate for (default: tiktok + linkedin)
92
+ * @returns Array of variant metadata (one entry per platform, deduplicated files)
93
+ */
94
+ export declare function generatePlatformVariants(inputPath: string, outputDir: string, slug: string, platforms?: Platform[]): Promise<{
95
+ platform: Platform;
96
+ aspectRatio: AspectRatio;
97
+ path: string;
98
+ width: number;
99
+ height: number;
100
+ }[]>;
101
+ //# sourceMappingURL=aspectRatio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aspectRatio.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/aspectRatio.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAA;AAEzD,6DAA6D;AAC7D,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,UAAU,GACV,SAAS,GACT,SAAS,CAAA;AAEb;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,WAAW,CAQzD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAK7E,CAAA;AAED,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAiDD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAuCjB;AAiJD;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,QAAQ,EAA2B,GAC7C,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAwC1G"}