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
@@ -1,339 +0,0 @@
1
- import { execFile } from 'child_process';
2
- import { promises as fs } from 'fs';
3
- import pathMod from 'path';
4
- import logger from '../../config/logger';
5
- import { detectWebcamRegion, getVideoResolution } from './faceDetection';
6
- import { getFFmpegPath } from '../../config/ffmpegResolver.js';
7
- const ffmpegPath = getFFmpegPath();
8
- /**
9
- * Maps each platform to its preferred aspect ratio.
10
- * Multiple platforms may share a ratio (e.g. TikTok + Reels both use 9:16),
11
- * which lets {@link generatePlatformVariants} deduplicate encodes.
12
- */
13
- export const PLATFORM_RATIOS = {
14
- 'tiktok': '9:16',
15
- 'youtube-shorts': '9:16',
16
- 'instagram-reels': '9:16',
17
- 'instagram-feed': '4:5',
18
- 'linkedin': '1:1',
19
- 'youtube': '16:9',
20
- 'twitter': '1:1',
21
- };
22
- /**
23
- * Canonical pixel dimensions for each aspect ratio.
24
- * Width is always 1080 px for non-landscape ratios (the standard vertical
25
- * video width); landscape stays at 1920×1080 for full HD.
26
- */
27
- export const DIMENSIONS = {
28
- '16:9': { width: 1920, height: 1080 },
29
- '9:16': { width: 1080, height: 1920 },
30
- '1:1': { width: 1080, height: 1080 },
31
- '4:5': { width: 1080, height: 1350 },
32
- };
33
- // ── Helpers ──────────────────────────────────────────────────────────────────
34
- /**
35
- * Build the FFmpeg `-vf` filter string for a simple center-crop conversion.
36
- *
37
- * This is the **fallback** used when smart layout (webcam detection + split-screen)
38
- * is unavailable. It center-crops the source frame to the target aspect ratio,
39
- * discarding content on the sides (or top/bottom).
40
- *
41
- * **Letterbox mode**: instead of cropping, scales the video to fit inside the
42
- * target dimensions and pads the remaining space with black bars. Useful when
43
- * you don't want to lose any content (e.g. screen recordings with important
44
- * edges).
45
- *
46
- * **Crop formulas** assume a 16:9 landscape source. `ih` = input height,
47
- * `iw` = input width. We compute the crop width from the height to maintain
48
- * the target ratio, then center the crop horizontally.
49
- *
50
- * @param targetRatio - The desired output aspect ratio
51
- * @param letterbox - If true, pad with black bars instead of cropping
52
- * @returns An FFmpeg `-vf` filter string
53
- */
54
- function buildCropFilter(targetRatio, letterbox) {
55
- if (letterbox) {
56
- const { width, height } = DIMENSIONS[targetRatio];
57
- // Scale to fit within target dimensions, then pad with black bars
58
- return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`;
59
- }
60
- switch (targetRatio) {
61
- case '9:16':
62
- // Center-crop landscape to portrait: crop width = ih*9/16, keep full height
63
- return 'crop=ih*9/16:ih:(iw-ih*9/16)/2:0,scale=1080:1920';
64
- case '1:1':
65
- // Center-crop to square: use height as the dimension (smaller axis for 16:9)
66
- return 'crop=ih:ih:(iw-ih)/2:0,scale=1080:1080';
67
- case '4:5':
68
- // Center-crop landscape to 4:5: crop width = ih*4/5, keep full height
69
- return 'crop=ih*4/5:ih:(iw-ih*4/5)/2:0,scale=1080:1350';
70
- case '16:9':
71
- // Same ratio — just ensure standard dimensions
72
- return 'scale=1920:1080';
73
- }
74
- }
75
- // ── Public API ───────────────────────────────────────────────────────────────
76
- /**
77
- * Convert a video's aspect ratio using FFmpeg center-crop.
78
- *
79
- * - 16:9 → 9:16: crops the center column to portrait
80
- * - 16:9 → 1:1: crops to a center square
81
- * - Same ratio: stream-copies without re-encoding
82
- *
83
- * @returns The output path on success
84
- */
85
- export async function convertAspectRatio(inputPath, outputPath, targetRatio, options = {}) {
86
- const outputDir = pathMod.dirname(outputPath);
87
- await fs.mkdir(outputDir, { recursive: true });
88
- const sourceRatio = '16:9'; // our videos are always landscape
89
- // Same ratio — stream copy
90
- if (sourceRatio === targetRatio && !options.letterbox) {
91
- logger.info(`Aspect ratio already ${targetRatio}, copying → ${outputPath}`);
92
- await fs.copyFile(inputPath, outputPath);
93
- return outputPath;
94
- }
95
- const vf = buildCropFilter(targetRatio, options.letterbox ?? false);
96
- logger.info(`Converting aspect ratio to ${targetRatio} (filter: ${vf}) → ${outputPath}`);
97
- const args = [
98
- '-y',
99
- '-i', inputPath,
100
- '-vf', vf,
101
- '-c:v', 'libx264',
102
- '-preset', 'ultrafast',
103
- '-crf', '23',
104
- '-c:a', 'copy',
105
- '-threads', '4',
106
- outputPath,
107
- ];
108
- return new Promise((resolve, reject) => {
109
- execFile(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {
110
- if (error) {
111
- logger.error(`Aspect ratio conversion failed: ${stderr || error.message}`);
112
- reject(new Error(`Aspect ratio conversion failed: ${stderr || error.message}`));
113
- return;
114
- }
115
- logger.info(`Aspect ratio conversion complete: ${outputPath}`);
116
- resolve(outputPath);
117
- });
118
- });
119
- }
120
- /**
121
- * Shared smart conversion: detects a webcam overlay in the source video and
122
- * builds a **split-screen** layout (screen on top, webcam on bottom).
123
- *
124
- * ### Why split-screen?
125
- * Screen recordings with a webcam overlay (e.g. top-right corner) waste space
126
- * when naively center-cropped to portrait/square. The split-screen approach
127
- * gives the screen content and webcam each their own dedicated panel, making
128
- * both fully visible in a narrow frame.
129
- *
130
- * ### Algorithm
131
- * 1. Run {@link detectWebcamRegion} to find the webcam bounding box.
132
- * 2. **Screen crop**: exclude the webcam columns so only the screen content
133
- * remains, then scale to `targetW × screenH` (letterboxing if needed).
134
- * 3. **Webcam crop**: aspect-ratio-match the webcam region to `targetW × camH`.
135
- * If the webcam is wider than the target, we keep full height and
136
- * center-crop width; if taller, we keep full width and center-crop height.
137
- * This ensures the webcam fills its panel edge-to-edge with **no black bars**.
138
- * 4. **vstack**: vertically stack `[screen][cam]` into the final frame.
139
- *
140
- * Falls back to simple center-crop ({@link buildCropFilter}) if no webcam is
141
- * detected.
142
- *
143
- * @param inputPath - Source video (assumed 16:9 landscape with optional webcam overlay)
144
- * @param outputPath - Destination path for the converted video
145
- * @param config - Layout geometry (see {@link SmartLayoutConfig})
146
- * @returns The output path on success
147
- */
148
- async function convertWithSmartLayout(inputPath, outputPath, config) {
149
- const { label, targetW, screenH, camH, fallbackRatio } = config;
150
- const outputDir = pathMod.dirname(outputPath);
151
- await fs.mkdir(outputDir, { recursive: true });
152
- const webcam = await detectWebcamRegion(inputPath);
153
- if (!webcam) {
154
- logger.info(`[${label}] No webcam found, falling back to center-crop`);
155
- return convertAspectRatio(inputPath, outputPath, fallbackRatio);
156
- }
157
- const resolution = await getVideoResolution(inputPath);
158
- // Determine screen crop region (exclude webcam area using detected bounds)
159
- let screenCropX;
160
- let screenCropW;
161
- if (webcam.position === 'top-right' || webcam.position === 'bottom-right') {
162
- screenCropX = 0;
163
- screenCropW = webcam.x;
164
- }
165
- else {
166
- screenCropX = webcam.x + webcam.width;
167
- screenCropW = Math.max(0, resolution.width - screenCropX);
168
- }
169
- // Crop webcam to match target bottom-section aspect ratio, then scale to fill
170
- const targetAR = targetW / camH;
171
- const webcamAR = webcam.width / webcam.height;
172
- let faceX, faceY, faceW, faceH;
173
- if (webcamAR > targetAR) {
174
- // Webcam wider than target: keep full height, center-crop width
175
- faceH = webcam.height;
176
- faceW = Math.round(faceH * targetAR);
177
- faceX = webcam.x + Math.round((webcam.width - faceW) / 2);
178
- faceY = webcam.y;
179
- }
180
- else {
181
- // Webcam taller than target: keep full width, center-crop height
182
- faceW = webcam.width;
183
- faceH = Math.round(faceW / targetAR);
184
- faceX = webcam.x;
185
- faceY = webcam.y + Math.round((webcam.height - faceH) / 2);
186
- }
187
- const filterComplex = [
188
- `[0:v]crop=${screenCropW}:ih:${screenCropX}:0,scale=${targetW}:${screenH}:force_original_aspect_ratio=decrease,` +
189
- `pad=${targetW}:${screenH}:(ow-iw)/2:(oh-ih)/2:black[screen]`,
190
- `[0:v]crop=${faceW}:${faceH}:${faceX}:${faceY},scale=${targetW}:${camH}[cam]`,
191
- '[screen][cam]vstack[out]',
192
- ].join(';');
193
- logger.info(`[${label}] Split-screen layout: webcam at ${webcam.position} → ${outputPath}`);
194
- const args = [
195
- '-y',
196
- '-i', inputPath,
197
- '-filter_complex', filterComplex,
198
- '-map', '[out]',
199
- '-map', '0:a',
200
- '-c:v', 'libx264',
201
- '-preset', 'ultrafast',
202
- '-crf', '23',
203
- '-c:a', 'aac',
204
- '-b:a', '128k',
205
- '-threads', '4',
206
- outputPath,
207
- ];
208
- return new Promise((resolve, reject) => {
209
- execFile(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {
210
- if (error) {
211
- logger.error(`[${label}] FFmpeg failed: ${stderr || error.message}`);
212
- reject(new Error(`${label} conversion failed: ${stderr || error.message}`));
213
- return;
214
- }
215
- logger.info(`[${label}] Complete: ${outputPath}`);
216
- resolve(outputPath);
217
- });
218
- });
219
- }
220
- /**
221
- * Smart portrait (9:16) conversion → 1080×1920.
222
- *
223
- * Screen panel: 1080×1248 (65%), Webcam panel: 1080×672 (35%).
224
- * Total: 1080×1920 — standard TikTok / Reels / Shorts dimensions.
225
- *
226
- * Falls back to center-crop 9:16 if no webcam is detected.
227
- *
228
- * @param inputPath - Source landscape video
229
- * @param outputPath - Destination path for the portrait video
230
- */
231
- export async function convertToPortraitSmart(inputPath, outputPath) {
232
- return convertWithSmartLayout(inputPath, outputPath, {
233
- label: 'SmartPortrait',
234
- targetW: 1080,
235
- screenH: 1248,
236
- camH: 672,
237
- fallbackRatio: '9:16',
238
- });
239
- }
240
- /**
241
- * Smart square (1:1) conversion → 1080×1080.
242
- *
243
- * Screen panel: 1080×700 (65%), Webcam panel: 1080×380 (35%).
244
- * Total: 1080×1080 — standard LinkedIn / Twitter square format.
245
- *
246
- * Falls back to center-crop 1:1 if no webcam is detected.
247
- *
248
- * @param inputPath - Source landscape video
249
- * @param outputPath - Destination path for the square video
250
- */
251
- export async function convertToSquareSmart(inputPath, outputPath) {
252
- return convertWithSmartLayout(inputPath, outputPath, {
253
- label: 'SmartSquare',
254
- targetW: 1080,
255
- screenH: 700,
256
- camH: 380,
257
- fallbackRatio: '1:1',
258
- });
259
- }
260
- /**
261
- * Smart feed (4:5) conversion → 1080×1350.
262
- *
263
- * Screen panel: 1080×878 (65%), Webcam panel: 1080×472 (35%).
264
- * Total: 1080×1350 — Instagram feed's preferred tall format.
265
- *
266
- * Falls back to center-crop 4:5 if no webcam is detected.
267
- *
268
- * @param inputPath - Source landscape video
269
- * @param outputPath - Destination path for the 4:5 video
270
- */
271
- export async function convertToFeedSmart(inputPath, outputPath) {
272
- return convertWithSmartLayout(inputPath, outputPath, {
273
- label: 'SmartFeed',
274
- targetW: 1080,
275
- screenH: 878,
276
- camH: 472,
277
- fallbackRatio: '4:5',
278
- });
279
- }
280
- /**
281
- * Generate platform-specific aspect-ratio variants of a short clip.
282
- *
283
- * ### Routing logic
284
- * 1. Maps each requested platform to its aspect ratio via {@link PLATFORM_RATIOS}.
285
- * 2. **Deduplicates by ratio** — if TikTok and Reels both need 9:16, only one
286
- * encode is performed and both platforms reference the same output file.
287
- * 3. Skips 16:9 entirely since the source is already landscape.
288
- * 4. Routes each ratio to its smart converter (portrait / square / feed) for
289
- * split-screen layout, falling back to {@link convertAspectRatio} for any
290
- * ratio without a smart converter.
291
- *
292
- * @param inputPath - Source video (16:9 landscape)
293
- * @param outputDir - Directory to write variant files into
294
- * @param slug - Base filename slug (e.g. "my-video-short-1")
295
- * @param platforms - Platforms to generate for (default: tiktok + linkedin)
296
- * @returns Array of variant metadata (one entry per platform, deduplicated files)
297
- */
298
- export async function generatePlatformVariants(inputPath, outputDir, slug, platforms = ['tiktok', 'linkedin']) {
299
- await fs.mkdir(outputDir, { recursive: true });
300
- // Deduplicate by aspect ratio to avoid redundant encodes
301
- const ratioMap = new Map();
302
- for (const p of platforms) {
303
- const ratio = PLATFORM_RATIOS[p];
304
- if (ratio === '16:9')
305
- continue; // skip — original is already 16:9
306
- const list = ratioMap.get(ratio) ?? [];
307
- list.push(p);
308
- ratioMap.set(ratio, list);
309
- }
310
- const variants = [];
311
- for (const [ratio, associatedPlatforms] of ratioMap) {
312
- const suffix = ratio === '9:16' ? 'portrait' : ratio === '4:5' ? 'feed' : 'square';
313
- const outPath = pathMod.join(outputDir, `${slug}-${suffix}.mp4`);
314
- try {
315
- if (ratio === '9:16') {
316
- await convertToPortraitSmart(inputPath, outPath);
317
- }
318
- else if (ratio === '1:1') {
319
- await convertToSquareSmart(inputPath, outPath);
320
- }
321
- else if (ratio === '4:5') {
322
- await convertToFeedSmart(inputPath, outPath);
323
- }
324
- else {
325
- await convertAspectRatio(inputPath, outPath, ratio);
326
- }
327
- const dims = DIMENSIONS[ratio];
328
- for (const p of associatedPlatforms) {
329
- variants.push({ platform: p, aspectRatio: ratio, path: outPath, width: dims.width, height: dims.height });
330
- }
331
- }
332
- catch (err) {
333
- const message = err instanceof Error ? err.message : String(err);
334
- logger.warn(`Skipping ${ratio} variant for ${slug}: ${message}`);
335
- }
336
- }
337
- return variants;
338
- }
339
- //# sourceMappingURL=aspectRatio.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"aspectRatio.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/aspectRatio.ts"],"names":[],"mappings":"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"}
@@ -1,16 +0,0 @@
1
- export interface ExtractAudioOptions {
2
- /** Output format: 'mp3' (default, smaller) or 'wav' */
3
- format?: 'mp3' | 'wav';
4
- }
5
- /**
6
- * Extract audio from a video file to mono MP3 at 64kbps (small enough for Whisper).
7
- * A 10-minute video produces ~5MB MP3 vs ~115MB WAV.
8
- */
9
- export declare function extractAudio(videoPath: string, outputPath: string, options?: ExtractAudioOptions): Promise<string>;
10
- /**
11
- * Split an audio file into chunks of approximately `maxChunkSizeMB` each.
12
- * Uses ffmpeg to split by duration calculated from the file size.
13
- * Returns an array of chunk file paths.
14
- */
15
- export declare function splitAudioIntoChunks(audioPath: string, maxChunkSizeMB?: number): Promise<string[]>;
16
- //# sourceMappingURL=audioExtraction.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"audioExtraction.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/audioExtraction.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAW,GAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAyCnB"}
@@ -1,87 +0,0 @@
1
- import ffmpeg from 'fluent-ffmpeg';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import logger from '../../config/logger';
5
- import { getFFmpegPath, getFFprobePath } from '../../config/ffmpegResolver.js';
6
- const ffmpegPath = getFFmpegPath();
7
- const ffprobePath = getFFprobePath();
8
- ffmpeg.setFfmpegPath(ffmpegPath);
9
- ffmpeg.setFfprobePath(ffprobePath);
10
- /**
11
- * Extract audio from a video file to mono MP3 at 64kbps (small enough for Whisper).
12
- * A 10-minute video produces ~5MB MP3 vs ~115MB WAV.
13
- */
14
- export async function extractAudio(videoPath, outputPath, options = {}) {
15
- const { format = 'mp3' } = options;
16
- const outputDir = path.dirname(outputPath);
17
- await fs.mkdir(outputDir, { recursive: true });
18
- logger.info(`Extracting audio (${format}): ${videoPath} → ${outputPath}`);
19
- return new Promise((resolve, reject) => {
20
- const command = ffmpeg(videoPath).noVideo().audioChannels(1);
21
- if (format === 'mp3') {
22
- command.audioCodec('libmp3lame').audioBitrate('64k').audioFrequency(16000);
23
- }
24
- else {
25
- command.audioCodec('pcm_s16le').audioFrequency(16000);
26
- }
27
- command
28
- .output(outputPath)
29
- .on('end', () => {
30
- logger.info(`Audio extraction complete: ${outputPath}`);
31
- resolve(outputPath);
32
- })
33
- .on('error', (err) => {
34
- logger.error(`Audio extraction failed: ${err.message}`);
35
- reject(new Error(`Audio extraction failed: ${err.message}`));
36
- })
37
- .run();
38
- });
39
- }
40
- /**
41
- * Split an audio file into chunks of approximately `maxChunkSizeMB` each.
42
- * Uses ffmpeg to split by duration calculated from the file size.
43
- * Returns an array of chunk file paths.
44
- */
45
- export async function splitAudioIntoChunks(audioPath, maxChunkSizeMB = 24) {
46
- const stats = await fs.stat(audioPath);
47
- const fileSizeMB = stats.size / (1024 * 1024);
48
- if (fileSizeMB <= maxChunkSizeMB) {
49
- return [audioPath];
50
- }
51
- const duration = await getAudioDuration(audioPath);
52
- const numChunks = Math.ceil(fileSizeMB / maxChunkSizeMB);
53
- const chunkDuration = duration / numChunks;
54
- const ext = path.extname(audioPath);
55
- const base = audioPath.slice(0, -ext.length);
56
- const chunkPaths = [];
57
- logger.info(`Splitting ${fileSizeMB.toFixed(1)}MB audio into ${numChunks} chunks ` +
58
- `(~${chunkDuration.toFixed(0)}s each)`);
59
- for (let i = 0; i < numChunks; i++) {
60
- const startTime = i * chunkDuration;
61
- const chunkPath = `${base}_chunk${i}${ext}`;
62
- chunkPaths.push(chunkPath);
63
- await new Promise((resolve, reject) => {
64
- const cmd = ffmpeg(audioPath)
65
- .setStartTime(startTime)
66
- .setDuration(chunkDuration)
67
- .audioCodec('copy')
68
- .output(chunkPath)
69
- .on('end', () => resolve())
70
- .on('error', (err) => reject(new Error(`Chunk split failed: ${err.message}`)));
71
- cmd.run();
72
- });
73
- logger.info(`Created chunk ${i + 1}/${numChunks}: ${chunkPath}`);
74
- }
75
- return chunkPaths;
76
- }
77
- /** Get the duration of an audio file in seconds using ffprobe. */
78
- function getAudioDuration(audioPath) {
79
- return new Promise((resolve, reject) => {
80
- ffmpeg.ffprobe(audioPath, (err, metadata) => {
81
- if (err)
82
- return reject(new Error(`ffprobe failed: ${err.message}`));
83
- resolve(metadata.format.duration ?? 0);
84
- });
85
- });
86
- }
87
- //# sourceMappingURL=audioExtraction.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"audioExtraction.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/audioExtraction.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,qBAAqB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAE/E,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;AACnC,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;AACrC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;AACjC,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;AAOnC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAiB,EACjB,UAAkB,EAClB,UAA+B,EAAE;IAEjC,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,MAAM,CAAC,IAAI,CAAC,qBAAqB,MAAM,MAAM,SAAS,MAAM,UAAU,EAAE,CAAC,CAAC;IAE1E,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QAE7D,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACrB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC;QAED,OAAO;aACJ,MAAM,CAAC,UAAU,CAAC;aAClB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACd,MAAM,CAAC,IAAI,CAAC,8BAA8B,UAAU,EAAE,CAAC,CAAC;YACxD,OAAO,CAAC,UAAU,CAAC,CAAC;QACtB,CAAC,CAAC;aACD,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACnB,MAAM,CAAC,KAAK,CAAC,4BAA4B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACxD,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC;aACD,GAAG,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,SAAiB,EACjB,iBAAyB,EAAE;IAE3B,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAE9C,IAAI,UAAU,IAAI,cAAc,EAAE,CAAC;QACjC,OAAO,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,cAAc,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;IAE3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,MAAM,CAAC,IAAI,CACT,aAAa,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,SAAS,UAAU;QACtE,KAAK,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CACvC,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,CAAC,GAAG,aAAa,CAAC;QACpC,MAAM,SAAS,GAAG,GAAG,IAAI,SAAS,CAAC,GAAG,GAAG,EAAE,CAAC;QAC5C,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE3B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC;iBAC1B,YAAY,CAAC,SAAS,CAAC;iBACvB,WAAW,CAAC,aAAa,CAAC;iBAC1B,UAAU,CAAC,MAAM,CAAC;iBAClB,MAAM,CAAC,SAAS,CAAC;iBACjB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;iBAC1B,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACjF,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,kEAAkE;AAClE,SAAS,gBAAgB,CAAC,SAAiB;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE;YAC1C,IAAI,GAAG;gBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,8 +0,0 @@
1
- /**
2
- * Burn ASS subtitles into video (hard-coded subtitles).
3
- * Uses direct execFile instead of fluent-ffmpeg to avoid Windows path escaping issues.
4
- * Copies the ASS file to a temp dir and uses a relative path to dodge the Windows
5
- * drive-letter colon being parsed as an FFmpeg filter option separator.
6
- */
7
- export declare function burnCaptions(videoPath: string, assPath: string, outputPath: string): Promise<string>;
8
- //# sourceMappingURL=captionBurning.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"captionBurning.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/captionBurning.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA6DjB"}
@@ -1,72 +0,0 @@
1
- import { execFile } from 'child_process';
2
- import { promises as fs } from 'fs';
3
- import pathMod from 'path';
4
- import os from 'os';
5
- import { fileURLToPath } from 'url';
6
- import logger from '../../config/logger';
7
- import { getFFmpegPath } from '../../config/ffmpegResolver.js';
8
- const ffmpegPath = getFFmpegPath();
9
- const __dirname = pathMod.dirname(fileURLToPath(import.meta.url));
10
- const FONTS_DIR = pathMod.resolve(__dirname, '..', '..', '..', 'assets', 'fonts');
11
- /**
12
- * Burn ASS subtitles into video (hard-coded subtitles).
13
- * Uses direct execFile instead of fluent-ffmpeg to avoid Windows path escaping issues.
14
- * Copies the ASS file to a temp dir and uses a relative path to dodge the Windows
15
- * drive-letter colon being parsed as an FFmpeg filter option separator.
16
- */
17
- export async function burnCaptions(videoPath, assPath, outputPath) {
18
- const outputDir = pathMod.dirname(outputPath);
19
- await fs.mkdir(outputDir, { recursive: true });
20
- logger.info(`Burning captions into video → ${outputPath}`);
21
- // Create a dedicated temp dir so we can use colon-free relative paths
22
- const workDir = await fs.mkdtemp(pathMod.join(os.tmpdir(), 'caption-'));
23
- const tempAss = pathMod.join(workDir, 'captions.ass');
24
- const tempOutput = pathMod.join(workDir, 'output.mp4');
25
- await fs.copyFile(assPath, tempAss);
26
- // Copy bundled fonts so libass can find them via fontsdir=.
27
- const fontFiles = await fs.readdir(FONTS_DIR);
28
- for (const f of fontFiles) {
29
- if (f.endsWith('.ttf') || f.endsWith('.otf')) {
30
- await fs.copyFile(pathMod.join(FONTS_DIR, f), pathMod.join(workDir, f));
31
- }
32
- }
33
- // Use just the filename — no drive letter, no colons
34
- const args = [
35
- '-y',
36
- '-i', videoPath,
37
- '-vf', 'ass=captions.ass:fontsdir=.',
38
- '-c:a', 'copy',
39
- '-c:v', 'libx264',
40
- '-preset', 'ultrafast',
41
- '-crf', '23',
42
- '-threads', '4',
43
- tempOutput,
44
- ];
45
- return new Promise((resolve, reject) => {
46
- execFile(ffmpegPath, args, { cwd: workDir, maxBuffer: 10 * 1024 * 1024 }, async (error, _stdout, stderr) => {
47
- const cleanup = async () => {
48
- const files = await fs.readdir(workDir).catch(() => []);
49
- for (const f of files) {
50
- await fs.unlink(pathMod.join(workDir, f)).catch(() => { });
51
- }
52
- await fs.rmdir(workDir).catch(() => { });
53
- };
54
- if (error) {
55
- await cleanup();
56
- logger.error(`Caption burning failed: ${stderr || error.message}`);
57
- reject(new Error(`Caption burning failed: ${stderr || error.message}`));
58
- return;
59
- }
60
- try {
61
- await fs.rename(tempOutput, outputPath);
62
- }
63
- catch {
64
- await fs.copyFile(tempOutput, outputPath);
65
- }
66
- await cleanup();
67
- logger.info(`Captions burned: ${outputPath}`);
68
- resolve(outputPath);
69
- });
70
- });
71
- }
72
- //# sourceMappingURL=captionBurning.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"captionBurning.js","sourceRoot":"","sources":["../../../src/tools/ffmpeg/captionBurning.ts"],"names":[],"mappings":"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"}
@@ -1,38 +0,0 @@
1
- import { ShortSegment } from '../../types';
2
- /**
3
- * Extract a single clip segment using re-encode for frame-accurate timing.
4
- *
5
- * ### Why re-encode instead of `-c copy`?
6
- * Stream copy (`-c copy`) seeks to the nearest **keyframe** before the
7
- * requested start time, which creates a PTS offset between the clip's actual
8
- * start and the timestamp the caption generator assumes. This causes
9
- * captions to be out of sync with the audio — especially visible in
10
- * landscape-captioned shorts where there's no intermediate re-encode to
11
- * normalize PTS (the portrait path gets an extra re-encode via aspect-ratio
12
- * conversion which masks the issue).
13
- *
14
- * Re-encoding with `trim` + `setpts=PTS-STARTPTS` guarantees:
15
- * - The clip starts at **exactly** `bufferedStart` (not the nearest keyframe)
16
- * - Output PTS starts at 0 with no offset
17
- * - Caption timestamps align perfectly with both audio and video
18
- *
19
- * @param buffer Seconds of padding added before start and after end (default 1.0)
20
- */
21
- export declare function extractClip(videoPath: string, start: number, end: number, outputPath: string, buffer?: number): Promise<string>;
22
- /**
23
- * Extract multiple non-contiguous segments and concatenate them into one clip.
24
- * Each segment is padded by `buffer` seconds on both sides for smoother cuts.
25
- * Re-encodes and uses concat demuxer for clean joins.
26
- * @param buffer Seconds of padding added before start and after end of each segment (default 1.0)
27
- */
28
- export declare function extractCompositeClip(videoPath: string, segments: ShortSegment[], outputPath: string, buffer?: number): Promise<string>;
29
- /**
30
- * Extract multiple non-contiguous segments and concatenate them with crossfade
31
- * transitions using FFmpeg xfade/acrossfade filters.
32
- * Falls back to extractCompositeClip if only one segment is provided.
33
- *
34
- * @param transitionDuration Crossfade duration in seconds (default 0.5)
35
- * @param buffer Seconds of padding added before/after each segment (default 1.0)
36
- */
37
- export declare function extractCompositeClipWithTransitions(videoPath: string, segments: ShortSegment[], outputPath: string, transitionDuration?: number, buffer?: number): Promise<string>;
38
- //# sourceMappingURL=clipExtraction.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clipExtraction.d.ts","sourceRoot":"","sources":["../../../src/tools/ffmpeg/clipExtraction.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkC3C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,YAAY,EAAE,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CAgEjB;AAED;;;;;;;GAOG;AACH,wBAAsB,mCAAmC,CACvD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,YAAY,EAAE,EACxB,UAAU,EAAE,MAAM,EAClB,kBAAkB,GAAE,MAAY,EAChC,MAAM,GAAE,MAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CA6FjB"}