@empiricalrun/test-gen 0.76.0 → 0.78.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 (237) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/agent/base/index.d.ts +25 -21
  3. package/dist/agent/base/index.d.ts.map +1 -1
  4. package/dist/agent/base/index.js +50 -37
  5. package/dist/agent/browsing/run.d.ts +1 -2
  6. package/dist/agent/browsing/run.d.ts.map +1 -1
  7. package/dist/agent/browsing/run.js +3 -9
  8. package/dist/agent/browsing/utils.d.ts +2 -9
  9. package/dist/agent/browsing/utils.d.ts.map +1 -1
  10. package/dist/agent/browsing/utils.js +5 -109
  11. package/dist/agent/chat/agent-loop.d.ts +5 -5
  12. package/dist/agent/chat/agent-loop.d.ts.map +1 -1
  13. package/dist/agent/chat/agent-loop.js +3 -8
  14. package/dist/agent/chat/exports.d.ts +6 -5
  15. package/dist/agent/chat/exports.d.ts.map +1 -1
  16. package/dist/agent/chat/exports.js +4 -9
  17. package/dist/agent/chat/index.d.ts +2 -2
  18. package/dist/agent/chat/index.d.ts.map +1 -1
  19. package/dist/agent/chat/index.js +23 -35
  20. package/dist/agent/chat/models.d.ts +0 -2
  21. package/dist/agent/chat/models.d.ts.map +1 -1
  22. package/dist/agent/chat/models.js +12 -26
  23. package/dist/agent/chat/prompt/pw-utils-docs.d.ts +1 -1
  24. package/dist/agent/chat/prompt/pw-utils-docs.d.ts.map +1 -1
  25. package/dist/agent/chat/prompt/pw-utils-docs.js +52 -0
  26. package/dist/agent/chat/prompt/repo.d.ts.map +1 -1
  27. package/dist/agent/chat/prompt/repo.js +11 -22
  28. package/dist/agent/chat/prompt/test-case-def.d.ts +2 -0
  29. package/dist/agent/chat/prompt/test-case-def.d.ts.map +1 -0
  30. package/dist/agent/chat/prompt/test-case-def.js +44 -0
  31. package/dist/agent/chat/state.d.ts +8 -14
  32. package/dist/agent/chat/state.d.ts.map +1 -1
  33. package/dist/agent/chat/state.js +15 -60
  34. package/dist/agent/chat/utils.d.ts +2 -2
  35. package/dist/agent/chat/utils.d.ts.map +1 -1
  36. package/dist/agent/chat/utils.js +14 -7
  37. package/dist/agent/cli.d.ts.map +1 -1
  38. package/dist/agent/cli.js +49 -58
  39. package/dist/agent/code-review/executor/index.d.ts +5 -0
  40. package/dist/agent/code-review/executor/index.d.ts.map +1 -0
  41. package/dist/agent/code-review/executor/index.js +13 -0
  42. package/dist/agent/code-review/index.d.ts +8 -3
  43. package/dist/agent/code-review/index.d.ts.map +1 -1
  44. package/dist/agent/code-review/index.js +118 -21
  45. package/dist/agent/code-review/parser.d.ts +5 -0
  46. package/dist/agent/code-review/parser.d.ts.map +1 -0
  47. package/dist/agent/code-review/parser.js +70 -0
  48. package/dist/agent/code-review/types.d.ts +36 -0
  49. package/dist/agent/code-review/types.d.ts.map +1 -0
  50. package/dist/agent/code-review/types.js +13 -0
  51. package/dist/agent/cua/index.d.ts.map +1 -1
  52. package/dist/agent/cua/index.js +18 -2
  53. package/dist/agent/cua/model.d.ts.map +1 -1
  54. package/dist/agent/cua/model.js +4 -1
  55. package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
  56. package/dist/agent/triage/index.d.ts +3 -3
  57. package/dist/agent/triage/index.d.ts.map +1 -1
  58. package/dist/agent/triage/index.js +16 -20
  59. package/dist/agent/video-analysis/executor/index.d.ts +5 -0
  60. package/dist/agent/video-analysis/executor/index.d.ts.map +1 -0
  61. package/dist/agent/video-analysis/executor/index.js +10 -0
  62. package/dist/agent/video-analysis/index.d.ts +2 -2
  63. package/dist/agent/video-analysis/index.d.ts.map +1 -1
  64. package/dist/agent/video-analysis/index.js +38 -13
  65. package/dist/artifacts/index.d.ts +1 -1
  66. package/dist/artifacts/index.d.ts.map +1 -1
  67. package/dist/artifacts/index.js +3 -1
  68. package/dist/artifacts/utils.d.ts.map +1 -1
  69. package/dist/bin/index.js +11 -21
  70. package/dist/constants/index.d.ts +14 -0
  71. package/dist/constants/index.d.ts.map +1 -1
  72. package/dist/constants/index.js +33 -1
  73. package/dist/file/server.d.ts +1 -3
  74. package/dist/file/server.d.ts.map +1 -1
  75. package/dist/file/server.js +0 -13
  76. package/dist/file-info/adapters/file-system/index.d.ts.map +1 -1
  77. package/dist/file-info/adapters/file-system/reader.d.ts.map +1 -1
  78. package/dist/file-info/adapters/file-system/reader.js +8 -1
  79. package/dist/file-info/adapters/github/index.d.ts.map +1 -1
  80. package/dist/file-info/adapters/github/reader.d.ts +1 -1
  81. package/dist/file-info/adapters/github/reader.d.ts.map +1 -1
  82. package/dist/file-info/adapters/github/reader.js +8 -5
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/tools/analyse-video/index.d.ts +5 -0
  85. package/dist/tools/analyse-video/index.d.ts.map +1 -0
  86. package/dist/tools/analyse-video/index.js +56 -0
  87. package/dist/tools/create-pull-request/index.js +4 -6
  88. package/dist/tools/create-pull-request/utils.d.ts +1 -1
  89. package/dist/tools/definitions/{fetch-video-analysis.d.ts → analyse-video.d.ts} +17 -12
  90. package/dist/tools/definitions/analyse-video.d.ts.map +1 -0
  91. package/dist/tools/definitions/analyse-video.js +60 -0
  92. package/dist/tools/definitions/review-pull-request.d.ts +3 -0
  93. package/dist/tools/definitions/review-pull-request.d.ts.map +1 -0
  94. package/dist/tools/definitions/review-pull-request.js +16 -0
  95. package/dist/tools/definitions/str_replace_editor.d.ts +1 -0
  96. package/dist/tools/definitions/str_replace_editor.d.ts.map +1 -1
  97. package/dist/tools/definitions/str_replace_editor.js +4 -1
  98. package/dist/tools/definitions/test-gen-browser.d.ts +0 -3
  99. package/dist/tools/definitions/test-gen-browser.d.ts.map +1 -1
  100. package/dist/tools/definitions/test-gen-browser.js +33 -8
  101. package/dist/tools/delete-file/index.d.ts.map +1 -1
  102. package/dist/tools/delete-file/index.js +1 -19
  103. package/dist/tools/executor/base.d.ts +32 -0
  104. package/dist/tools/executor/base.d.ts.map +1 -0
  105. package/dist/tools/executor/base.js +131 -0
  106. package/dist/tools/executor/index.d.ts +3 -22
  107. package/dist/tools/executor/index.d.ts.map +1 -1
  108. package/dist/tools/executor/index.js +7 -100
  109. package/dist/tools/executor/utils/checkpoint.d.ts +1 -1
  110. package/dist/tools/executor/utils/checkpoint.d.ts.map +1 -1
  111. package/dist/tools/executor/utils/checkpoint.js +6 -2
  112. package/dist/tools/executor/utils/git.d.ts +2 -2
  113. package/dist/tools/executor/utils/git.d.ts.map +1 -1
  114. package/dist/tools/executor/utils/git.js +7 -3
  115. package/dist/tools/executor/utils/index.d.ts +5 -3
  116. package/dist/tools/executor/utils/index.d.ts.map +1 -1
  117. package/dist/tools/executor/utils/index.js +23 -2
  118. package/dist/tools/fetch-session-diff/index.js +2 -2
  119. package/dist/tools/file-operations/create.d.ts.map +1 -1
  120. package/dist/tools/file-operations/create.js +1 -4
  121. package/dist/tools/file-operations/index.d.ts +2 -1
  122. package/dist/tools/file-operations/index.d.ts.map +1 -1
  123. package/dist/tools/file-operations/index.js +4 -1
  124. package/dist/tools/file-operations/insert.d.ts +1 -2
  125. package/dist/tools/file-operations/insert.d.ts.map +1 -1
  126. package/dist/tools/file-operations/insert.js +1 -4
  127. package/dist/tools/file-operations/replace.d.ts.map +1 -1
  128. package/dist/tools/file-operations/replace.js +21 -25
  129. package/dist/tools/file-operations/shared/helpers.d.ts +3 -5
  130. package/dist/tools/file-operations/shared/helpers.d.ts.map +1 -1
  131. package/dist/tools/file-operations/shared/helpers.js +1 -5
  132. package/dist/tools/grep/index.d.ts.map +1 -1
  133. package/dist/tools/grep/index.js +18 -11
  134. package/dist/tools/index.d.ts +5 -5
  135. package/dist/tools/index.d.ts.map +1 -1
  136. package/dist/tools/index.js +17 -16
  137. package/dist/tools/merge-conflicts/index.d.ts.map +1 -1
  138. package/dist/tools/merge-conflicts/index.js +1 -1
  139. package/dist/tools/rename-file/index.js +1 -1
  140. package/dist/tools/review-pull-request/index.d.ts.map +1 -1
  141. package/dist/tools/review-pull-request/index.js +44 -65
  142. package/dist/tools/run-test.d.ts.map +1 -1
  143. package/dist/tools/run-test.js +25 -3
  144. package/dist/tools/test-gen-browser.d.ts.map +1 -1
  145. package/dist/tools/test-gen-browser.js +51 -47
  146. package/dist/tools/upgrade-packages/index.d.ts.map +1 -1
  147. package/dist/tools/upgrade-packages/index.js +4 -0
  148. package/dist/tools/upgrade-packages/utils.d.ts +1 -0
  149. package/dist/tools/upgrade-packages/utils.d.ts.map +1 -1
  150. package/dist/tools/upgrade-packages/utils.js +1 -0
  151. package/dist/trace-utils/index.d.ts +1 -1
  152. package/dist/trace-utils/index.d.ts.map +1 -1
  153. package/dist/trace-utils/index.js +1 -1
  154. package/dist/utils/dedup/dedup-image.d.ts +22 -0
  155. package/dist/utils/dedup/dedup-image.d.ts.map +1 -0
  156. package/dist/utils/dedup/dedup-image.js +26 -0
  157. package/dist/utils/dedup/find-threshold.d.ts +2 -0
  158. package/dist/utils/dedup/find-threshold.d.ts.map +1 -0
  159. package/dist/utils/dedup/find-threshold.js +42 -0
  160. package/dist/utils/hash.d.ts +2 -0
  161. package/dist/utils/hash.d.ts.map +1 -0
  162. package/dist/utils/hash.js +24 -0
  163. package/dist/utils/model.d.ts +1 -1
  164. package/dist/utils/model.d.ts.map +1 -1
  165. package/dist/utils/model.js +7 -5
  166. package/dist/utils/repo-tree.d.ts +0 -1
  167. package/dist/utils/repo-tree.d.ts.map +1 -1
  168. package/dist/utils/repo-tree.js +2 -14
  169. package/dist/utils/slug.js +1 -1
  170. package/dist/video-core/agent-orchestrator.d.ts +13 -0
  171. package/dist/video-core/agent-orchestrator.d.ts.map +1 -0
  172. package/dist/video-core/agent-orchestrator.js +59 -0
  173. package/dist/video-core/index.d.ts +39 -0
  174. package/dist/video-core/index.d.ts.map +1 -0
  175. package/dist/video-core/index.js +134 -0
  176. package/dist/video-core/model-limits.d.ts +4 -0
  177. package/dist/video-core/model-limits.d.ts.map +1 -0
  178. package/dist/video-core/model-limits.js +73 -0
  179. package/dist/video-core/storage-manager.d.ts +5 -0
  180. package/dist/video-core/storage-manager.d.ts.map +1 -0
  181. package/dist/video-core/storage-manager.js +62 -0
  182. package/dist/video-core/types.d.ts +13 -0
  183. package/dist/video-core/types.d.ts.map +1 -0
  184. package/dist/video-core/types.js +2 -0
  185. package/dist/video-core/utils.d.ts +15 -0
  186. package/dist/video-core/utils.d.ts.map +1 -0
  187. package/dist/video-core/utils.js +194 -0
  188. package/dist/video-core/xml-parser.d.ts +3 -0
  189. package/dist/video-core/xml-parser.d.ts.map +1 -0
  190. package/dist/video-core/xml-parser.js +27 -0
  191. package/package.json +6 -6
  192. package/tsconfig.tsbuildinfo +1 -1
  193. package/dist/agent/chat/prompt/index.d.ts +0 -6
  194. package/dist/agent/chat/prompt/index.d.ts.map +0 -1
  195. package/dist/agent/chat/prompt/index.js +0 -200
  196. package/dist/agent/code-review/prompt.d.ts +0 -2
  197. package/dist/agent/code-review/prompt.d.ts.map +0 -1
  198. package/dist/agent/code-review/prompt.js +0 -55
  199. package/dist/agent/diagnosis-agent/index.d.ts +0 -11
  200. package/dist/agent/diagnosis-agent/index.d.ts.map +0 -1
  201. package/dist/agent/diagnosis-agent/index.js +0 -88
  202. package/dist/agent/diagnosis-agent/strict-mode-violation.d.ts +0 -10
  203. package/dist/agent/diagnosis-agent/strict-mode-violation.d.ts.map +0 -1
  204. package/dist/agent/diagnosis-agent/strict-mode-violation.js +0 -30
  205. package/dist/tools/definitions/extract-frames-from-video.d.ts +0 -39
  206. package/dist/tools/definitions/extract-frames-from-video.d.ts.map +0 -1
  207. package/dist/tools/definitions/extract-frames-from-video.js +0 -60
  208. package/dist/tools/definitions/fetch-video-analysis.d.ts.map +0 -1
  209. package/dist/tools/definitions/fetch-video-analysis.js +0 -61
  210. package/dist/tools/extract-frames-from-video/index.d.ts +0 -7
  211. package/dist/tools/extract-frames-from-video/index.d.ts.map +0 -1
  212. package/dist/tools/extract-frames-from-video/index.js +0 -145
  213. package/dist/tools/fetch-video-analysis/index.d.ts +0 -5
  214. package/dist/tools/fetch-video-analysis/index.d.ts.map +0 -1
  215. package/dist/tools/fetch-video-analysis/index.js +0 -149
  216. package/dist/tools/fetch-video-analysis/open-ai.d.ts +0 -6
  217. package/dist/tools/fetch-video-analysis/open-ai.d.ts.map +0 -1
  218. package/dist/tools/fetch-video-analysis/open-ai.js +0 -37
  219. package/dist/tools/fetch-video-analysis/utils.d.ts +0 -16
  220. package/dist/tools/fetch-video-analysis/utils.d.ts.map +0 -1
  221. package/dist/tools/fetch-video-analysis/utils.js +0 -121
  222. package/dist/tools/fetch-video-analysis/video-analysis.d.ts +0 -7
  223. package/dist/tools/fetch-video-analysis/video-analysis.d.ts.map +0 -1
  224. package/dist/tools/fetch-video-analysis/video-analysis.js +0 -70
  225. package/dist/tools/file-operations/shared/git-helper.d.ts +0 -4
  226. package/dist/tools/file-operations/shared/git-helper.d.ts.map +0 -1
  227. package/dist/tools/file-operations/shared/git-helper.js +0 -29
  228. package/dist/utils/dedup-image-fs.d.ts +0 -27
  229. package/dist/utils/dedup-image-fs.d.ts.map +0 -1
  230. package/dist/utils/dedup-image-fs.js +0 -88
  231. package/dist/utils/dedup-image.d.ts +0 -25
  232. package/dist/utils/dedup-image.d.ts.map +0 -1
  233. package/dist/utils/dedup-image.js +0 -80
  234. package/dist/utils/local-ffmpeg-client.d.ts +0 -27
  235. package/dist/utils/local-ffmpeg-client.d.ts.map +0 -1
  236. package/dist/utils/local-ffmpeg-client.js +0 -299
  237. package/eslint.config.mjs +0 -43
@@ -1,80 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.deduplicateImages = deduplicateImages;
7
- const pixelmatch_1 = __importDefault(require("pixelmatch"));
8
- const sharp_1 = __importDefault(require("sharp"));
9
- /**
10
- * Processes an array of base64 image objects and returns only unique images.
11
- * Two images are considered "similar" if the fraction of differing pixels (when resized)
12
- * is below the provided threshold.
13
- *
14
- * @param options - Configuration options
15
- * @param options.base64Images - An array of objects, each with a metadata field and a base64-encoded image string.
16
- * @param options.threshold - The maximum fraction of pixels allowed to differ to consider two images similar.
17
- * For example, 0.01 means that if less than 1% of the pixels differ, the images are considered duplicates.
18
- * Default is 0.001 (0.1%).
19
- * @param options.logPrefix - The ID of the test run, used for logging purposes.
20
- * @returns A promise that resolves to an array of unique image objects.
21
- */
22
- async function deduplicateImages({ base64Images, threshold = 0.001, logPrefix = "dedup-image", }) {
23
- const uniqueImages = [];
24
- // We'll store the raw pixel data (RGBA) and dimensions for the previous unique image.
25
- let previousImageData = null;
26
- let previousWidth = null;
27
- let previousHeight = null;
28
- for (const imgData of base64Images) {
29
- try {
30
- // Convert the base64 string to a Buffer.
31
- const buffer = Buffer.from(imgData.image, "base64");
32
- const imgMetadata = await (0, sharp_1.default)(buffer).metadata();
33
- const height = imgMetadata.height || 0;
34
- const width = imgMetadata.width || 0;
35
- // Resize the image to fixed dimensions and extract its raw RGBA pixel data.
36
- const { data } = await (0, sharp_1.default)(buffer)
37
- .ensureAlpha()
38
- .raw()
39
- .toBuffer({ resolveWithObject: true });
40
- let isDuplicate = false;
41
- // If there's a previously processed unique image, compare the current image with it.
42
- if (previousImageData &&
43
- previousWidth === width &&
44
- previousHeight === height) {
45
- // Only compare images if they have the same dimensions
46
- try {
47
- const diffPixels = (0, pixelmatch_1.default)(data, // current image pixel data
48
- previousImageData, // previous image pixel data
49
- null, // no diff image output is needed
50
- width, height, { threshold: 0.1 });
51
- // console.log("diffPixels", diffPixels);
52
- const totalPixels = height * width;
53
- const diffFraction = diffPixels / totalPixels;
54
- // console.log("diffFraction", diffFraction);
55
- // If the fraction of differing pixels is below (or equal to) the threshold,
56
- // consider the current image a duplicate.
57
- if (diffFraction <= threshold) {
58
- isDuplicate = true;
59
- }
60
- }
61
- catch (error) {
62
- console.error(`[${logPrefix}] Error comparing images:`, error);
63
- // If comparison fails, treat as non-duplicate
64
- isDuplicate = false;
65
- }
66
- }
67
- // If the image is not a duplicate, add it to the list and update the previous image data.
68
- if (!isDuplicate) {
69
- uniqueImages.push(imgData);
70
- previousImageData = data;
71
- previousWidth = width;
72
- previousHeight = height;
73
- }
74
- }
75
- catch (error) {
76
- console.error(`[${logPrefix}] Error processing image:`, error);
77
- }
78
- }
79
- return uniqueImages;
80
- }
@@ -1,27 +0,0 @@
1
- import { UniqueFrameWithMetadata } from "@empiricalrun/shared-types";
2
- export declare class LocalFFmpegClient {
3
- private static readonly MAX_VIDEO_DURATION_SECONDS;
4
- private static readonly CHUNK_DURATION_SECONDS;
5
- constructor();
6
- private checkFFmpegAvailability;
7
- private getVideoDuration;
8
- private validateVideoChunk;
9
- private downloadVideo;
10
- private ensureEmptyDir;
11
- private runFFmpegCommand;
12
- private createVideoChunks;
13
- private extractFramesToFiles;
14
- private processVideoChunks;
15
- storeUniqueFrames(uniqueFrames: UniqueFrameWithMetadata[], workingDir: string): Promise<string>;
16
- extractVideoFrames(videoUrl: string, outputDir: string, options?: {
17
- fps?: number;
18
- threshold?: number;
19
- startTime?: number;
20
- duration?: number;
21
- }): Promise<{
22
- totalFramesCount: number;
23
- uniqueFrames: UniqueFrameWithMetadata[];
24
- videoDurationSeconds: number;
25
- }>;
26
- }
27
- //# sourceMappingURL=local-ffmpeg-client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"local-ffmpeg-client.d.ts","sourceRoot":"","sources":["../../src/utils/local-ffmpeg-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAUrE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAW;IAC7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAU;;IAMxD,OAAO,CAAC,uBAAuB;YAWjB,gBAAgB;YAiBhB,kBAAkB;YAgClB,aAAa;YAeb,cAAc;YASd,gBAAgB;YAoBhB,iBAAiB;YAsEjB,oBAAoB;YAyCpB,kBAAkB;IA2D1B,iBAAiB,CACrB,YAAY,EAAE,uBAAuB,EAAE,EACvC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;IAiCZ,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GACA,OAAO,CAAC;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,uBAAuB,EAAE,CAAC;QACxC,oBAAoB,EAAE,MAAM,CAAC;KAC9B,CAAC;CA2GH"}
@@ -1,299 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.LocalFFmpegClient = void 0;
7
- const child_process_1 = require("child_process");
8
- const fs_1 = require("fs");
9
- const path_1 = __importDefault(require("path"));
10
- const util_1 = require("util");
11
- const dedup_image_fs_1 = require("./dedup-image-fs");
12
- const execAsync = (0, util_1.promisify)(child_process_1.exec);
13
- class LocalFFmpegClient {
14
- static MAX_VIDEO_DURATION_SECONDS = 15 * 60; // 15 minutes
15
- static CHUNK_DURATION_SECONDS = 2 * 60; // 2 minutes
16
- constructor() {
17
- this.checkFFmpegAvailability();
18
- }
19
- checkFFmpegAvailability() {
20
- try {
21
- (0, child_process_1.execSync)("ffmpeg -version", { stdio: "ignore" });
22
- (0, child_process_1.execSync)("ffprobe -version", { stdio: "ignore" });
23
- }
24
- catch (error) {
25
- throw new Error(`ffmpeg and ffprobe are required for video processing: ${error}`);
26
- }
27
- }
28
- async getVideoDuration(videoPath) {
29
- const command = `ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${videoPath}"`;
30
- try {
31
- const { stdout } = await execAsync(command);
32
- const duration = parseFloat(stdout.trim());
33
- if (isNaN(duration)) {
34
- throw new Error("Could not determine video duration");
35
- }
36
- return duration;
37
- }
38
- catch (error) {
39
- throw new Error(`Failed to get video duration: ${error}`);
40
- }
41
- }
42
- async validateVideoChunk(chunkPath) {
43
- const command = `ffprobe -v error -show_entries format=duration -of csv=p=0 "${chunkPath}"`;
44
- try {
45
- const { stdout, stderr } = await execAsync(command);
46
- if (stderr && stderr.toLowerCase().includes("error")) {
47
- console.warn(`Chunk validation found errors: ${stderr}`);
48
- return false;
49
- }
50
- const duration = parseFloat(stdout.trim());
51
- if (isNaN(duration) || duration <= 0) {
52
- console.warn(`Chunk has invalid duration: ${duration}`);
53
- return false;
54
- }
55
- // Check file size as additional validation
56
- const stats = await fs_1.promises.stat(chunkPath);
57
- if (stats.size < 1024) {
58
- // Less than 1KB is suspicious for a video chunk
59
- console.warn(`Chunk file size too small: ${stats.size} bytes`);
60
- return false;
61
- }
62
- return true;
63
- }
64
- catch (error) {
65
- console.warn(`Chunk validation failed: ${error}`);
66
- return false;
67
- }
68
- }
69
- async downloadVideo(videoUrl, outputPath) {
70
- console.log(`Downloading video from: ${videoUrl}`);
71
- const response = await fetch(videoUrl);
72
- if (!response.ok) {
73
- throw new Error(`Failed to download video: ${response.statusText}`);
74
- }
75
- const buffer = await response.arrayBuffer();
76
- await fs_1.promises.writeFile(outputPath, Buffer.from(buffer));
77
- }
78
- async ensureEmptyDir(dir) {
79
- try {
80
- await fs_1.promises.rm(dir, { recursive: true, force: true });
81
- }
82
- catch {
83
- // ignore
84
- }
85
- await fs_1.promises.mkdir(dir, { recursive: true });
86
- }
87
- async runFFmpegCommand({ inputPath, args, outputPath, }) {
88
- const quotedInput = `"${inputPath}"`;
89
- const output = outputPath ? ` "${outputPath}"` : "";
90
- const cmd = `ffmpeg -y -nostdin -i ${quotedInput} ${args.join(" ")}${output}`;
91
- try {
92
- await execAsync(cmd);
93
- }
94
- catch (error) {
95
- throw new Error(`ffmpeg command failed: ${cmd} => ${String(error)}`);
96
- }
97
- }
98
- async createVideoChunks(videoPath, outputDir, duration, startTime = 0) {
99
- const chunkPaths = [];
100
- const chunkCount = Math.ceil(duration / LocalFFmpegClient.CHUNK_DURATION_SECONDS);
101
- console.log(`Creating ${chunkCount} chunks of ${LocalFFmpegClient.CHUNK_DURATION_SECONDS} seconds each`);
102
- for (let i = 0; i < chunkCount; i++) {
103
- const chunkOffsetTime = i * LocalFFmpegClient.CHUNK_DURATION_SECONDS;
104
- const absoluteStartTime = startTime + chunkOffsetTime;
105
- const chunkPath = path_1.default.join(outputDir, `chunk_${i.toString().padStart(3, "0")}.mp4`);
106
- const remainingDuration = duration - chunkOffsetTime;
107
- const chunkDuration = Math.min(LocalFFmpegClient.CHUNK_DURATION_SECONDS, remainingDuration);
108
- try {
109
- await fs_1.promises.rm(chunkPath, { force: true });
110
- await this.runFFmpegCommand({
111
- inputPath: videoPath,
112
- args: [
113
- "-ss",
114
- String(absoluteStartTime),
115
- "-t",
116
- String(chunkDuration),
117
- "-c:v",
118
- "libx264",
119
- "-c:a",
120
- "aac",
121
- "-preset",
122
- "ultrafast",
123
- "-crf",
124
- "28",
125
- ],
126
- outputPath: chunkPath,
127
- });
128
- // Validate the created chunk
129
- const isValid = await this.validateVideoChunk(chunkPath);
130
- if (isValid) {
131
- chunkPaths.push(chunkPath);
132
- }
133
- else {
134
- console.warn(`Chunk ${i} appears corrupted, skipping...`);
135
- try {
136
- await fs_1.promises.unlink(chunkPath);
137
- }
138
- catch (unlinkError) {
139
- console.warn(`Failed to remove corrupted chunk: ${unlinkError}`);
140
- }
141
- }
142
- }
143
- catch (error) {
144
- throw new Error(`Failed to create chunk ${i}: ${error}`);
145
- }
146
- }
147
- return chunkPaths;
148
- }
149
- async extractFramesToFiles(videoPath, outputDir, chunkIndex, fps) {
150
- await this.ensureEmptyDir(outputDir);
151
- const framePrefix = chunkIndex !== undefined ? `chunk${chunkIndex}_frame` : "frame";
152
- const framePattern = path_1.default.join(outputDir, `${framePrefix}_%04d.png`);
153
- console.log(`Extracting frames with command: ffmpeg -i "${videoPath}" -vf "fps=${fps}" "${framePattern}"`);
154
- try {
155
- await this.runFFmpegCommand({
156
- inputPath: videoPath,
157
- args: ["-vf", `fps=${fps}`],
158
- outputPath: framePattern,
159
- });
160
- }
161
- catch (error) {
162
- throw new Error(`Frame extraction failed: ${error}`);
163
- }
164
- const files = await fs_1.promises.readdir(outputDir);
165
- const frameFiles = files
166
- .filter((file) => file.startsWith(framePrefix) && file.endsWith(".png"))
167
- .sort()
168
- .map((file) => path_1.default.join(outputDir, file));
169
- console.log(`Extracted ${frameFiles.length} frames from ${chunkIndex !== undefined ? `chunk ${chunkIndex}` : "video"}`);
170
- return frameFiles;
171
- }
172
- async processVideoChunks(chunkPaths, workingDir, fps) {
173
- const allFramePaths = [];
174
- const consolidatedFramesDir = path_1.default.join(workingDir, "consolidated_frames");
175
- await this.ensureEmptyDir(consolidatedFramesDir);
176
- let globalFrameIndex = 0;
177
- for (let i = 0; i < chunkPaths.length; i++) {
178
- const chunkPath = chunkPaths[i];
179
- const chunkFramesDir = path_1.default.join(workingDir, `chunk_${i}_frames`);
180
- console.log(`Processing chunk ${i + 1}/${chunkPaths.length}`);
181
- if (chunkPath === undefined) {
182
- throw new Error(`Chunk path is undefined for chunk ${i + 1}`);
183
- }
184
- try {
185
- const chunkFramePaths = await this.extractFramesToFiles(chunkPath, chunkFramesDir, i, fps);
186
- for (const framePath of chunkFramePaths) {
187
- const newFramePath = path_1.default.join(consolidatedFramesDir, `frame_${globalFrameIndex.toString().padStart(6, "0")}.png`);
188
- await fs_1.promises.rename(framePath, newFramePath);
189
- allFramePaths.push(newFramePath);
190
- globalFrameIndex++;
191
- }
192
- }
193
- catch (error) {
194
- console.warn(`Failed to process chunk ${i + 1}: ${error}. Skipping this chunk.`);
195
- // Continue with other chunks instead of failing completely
196
- }
197
- try {
198
- await fs_1.promises.rm(chunkFramesDir, { recursive: true });
199
- }
200
- catch (error) {
201
- console.warn(`Failed to clean up chunk frames directory: ${error}`);
202
- }
203
- if (global.gc) {
204
- global.gc();
205
- }
206
- }
207
- return allFramePaths;
208
- }
209
- async storeUniqueFrames(uniqueFrames, workingDir) {
210
- const uniqueFramesDir = path_1.default.join(workingDir, "unique_frames");
211
- await this.ensureEmptyDir(uniqueFramesDir);
212
- console.log(`Storing ${uniqueFrames.length} unique frames in ${uniqueFramesDir}`);
213
- for (let i = 0; i < uniqueFrames.length; i++) {
214
- const frame = uniqueFrames[i];
215
- if (!frame)
216
- continue;
217
- const originalPath = frame.metadata.path;
218
- const uniqueFramePath = path_1.default.join(uniqueFramesDir, `unique_frame_${i.toString().padStart(4, "0")}.png`);
219
- try {
220
- await fs_1.promises.copyFile(originalPath, uniqueFramePath);
221
- }
222
- catch (error) {
223
- console.warn(`Failed to copy frame ${originalPath} to ${uniqueFramePath}:`, error);
224
- }
225
- }
226
- console.log(`Stored ${uniqueFrames.length} unique frames in: ${uniqueFramesDir}`);
227
- return uniqueFramesDir;
228
- }
229
- async extractVideoFrames(videoUrl, outputDir, options) {
230
- const workingDir = path_1.default.join(process.cwd(), outputDir);
231
- const videoPath = path_1.default.join(workingDir, `video_${Date.now()}.webm`);
232
- const fps = options?.fps ?? 30;
233
- const threshold = options?.threshold ?? 0.001;
234
- const startTime = options?.startTime;
235
- const duration = options?.duration;
236
- try {
237
- await fs_1.promises.mkdir(workingDir, { recursive: true });
238
- await this.downloadVideo(videoUrl, videoPath);
239
- const videoDuration = await this.getVideoDuration(videoPath);
240
- console.log(`Video duration: ${Math.round(videoDuration)} seconds`);
241
- if (videoDuration > LocalFFmpegClient.MAX_VIDEO_DURATION_SECONDS) {
242
- throw new Error(`Video duration (${Math.round(videoDuration)}s) exceeds maximum allowed duration (${LocalFFmpegClient.MAX_VIDEO_DURATION_SECONDS}s)`);
243
- }
244
- // Validate and adjust time parameters
245
- let effectiveStartTime = 0;
246
- let effectiveDuration = videoDuration;
247
- if (startTime !== undefined) {
248
- if (startTime < 0) {
249
- throw new Error(`Start time cannot be negative: ${startTime}`);
250
- }
251
- if (startTime >= videoDuration) {
252
- throw new Error(`Start time (${startTime}s) exceeds video duration (${Math.round(videoDuration)}s)`);
253
- }
254
- effectiveStartTime = startTime;
255
- effectiveDuration = videoDuration - startTime;
256
- }
257
- if (duration !== undefined) {
258
- if (duration <= 0) {
259
- throw new Error(`Duration must be positive: ${duration}`);
260
- }
261
- const maxAllowedDuration = videoDuration - effectiveStartTime;
262
- if (duration > maxAllowedDuration) {
263
- console.warn(`Requested duration (${duration}s) exceeds available time (${maxAllowedDuration}s), truncating to fit`);
264
- effectiveDuration = maxAllowedDuration;
265
- }
266
- else {
267
- effectiveDuration = duration;
268
- }
269
- }
270
- console.log(`Effective extraction range: ${effectiveStartTime}s to ${effectiveStartTime + effectiveDuration}s`);
271
- const chunkPaths = await this.createVideoChunks(videoPath, workingDir, effectiveDuration, effectiveStartTime);
272
- const allFramePaths = await this.processVideoChunks(chunkPaths, workingDir, fps);
273
- const allFramesCount = allFramePaths.length;
274
- const uniqueImages = await (0, dedup_image_fs_1.deduplicateImageFiles)({
275
- imagePaths: allFramePaths,
276
- batchSize: 50,
277
- threshold,
278
- logPrefix: "ffmpeg-chunk-frame-dedup",
279
- });
280
- console.log(`Filtered to ${uniqueImages.length} unique frames from ${allFramesCount} total frames across ${chunkPaths.length} chunks`);
281
- if (uniqueImages.length === 0) {
282
- if (chunkPaths.length === 0) {
283
- throw new Error("No valid video chunks were created. The video may be corrupted or in an unsupported format.");
284
- }
285
- throw new Error("No frames were extracted from the video");
286
- }
287
- await this.storeUniqueFrames(uniqueImages, workingDir);
288
- return {
289
- totalFramesCount: allFramesCount,
290
- uniqueFrames: uniqueImages,
291
- videoDurationSeconds: videoDuration,
292
- };
293
- }
294
- catch (error) {
295
- throw new Error(`Frame extraction failed: ${error instanceof Error ? error.message : String(error)}`);
296
- }
297
- }
298
- }
299
- exports.LocalFFmpegClient = LocalFFmpegClient;
package/eslint.config.mjs DELETED
@@ -1,43 +0,0 @@
1
- import libraryConfig from "../eslint-config/library.mjs";
2
- import tsParser from "@typescript-eslint/parser";
3
- import js from "@eslint/js";
4
-
5
- export default [
6
- ...libraryConfig,
7
- {
8
- files: ["src/**/*.ts", "src/**/*.tsx"],
9
- languageOptions: {
10
- parser: tsParser,
11
- globals: {
12
- window: true,
13
- document: true,
14
- },
15
- parserOptions: {
16
- project: "./tsconfig.lint.json",
17
- tsconfigRootDir: import.meta.dirname,
18
- },
19
- },
20
- },
21
- {
22
- files: ["**/*.js"],
23
- ...js.configs.recommended,
24
- languageOptions: {
25
- globals: {
26
- browser: true,
27
- },
28
- parserOptions: {
29
- ecmaVersion: 2022,
30
- sourceType: "module",
31
- },
32
- },
33
- rules: {
34
- "@typescript-eslint/no-floating-promises": "off",
35
- "@typescript-eslint/no-misused-promises": "off",
36
- "@typescript-eslint/no-unsafe-assignment": "off",
37
- "@typescript-eslint/no-unsafe-call": "off",
38
- "@typescript-eslint/no-unsafe-member-access": "off",
39
- "@typescript-eslint/no-unsafe-return": "off",
40
- "no-unused-vars": "warn",
41
- },
42
- },
43
- ];