@empiricalrun/test-gen 0.75.0 → 0.77.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.
- package/CHANGELOG.md +48 -0
- package/dist/agent/base/index.d.ts +32 -21
- package/dist/agent/base/index.d.ts.map +1 -1
- package/dist/agent/base/index.js +100 -57
- package/dist/agent/browsing/run.d.ts +1 -2
- package/dist/agent/browsing/run.d.ts.map +1 -1
- package/dist/agent/browsing/run.js +3 -9
- package/dist/agent/browsing/utils.d.ts +2 -9
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +5 -109
- package/dist/agent/chat/agent-loop.d.ts +8 -7
- package/dist/agent/chat/agent-loop.d.ts.map +1 -1
- package/dist/agent/chat/agent-loop.js +7 -18
- package/dist/agent/chat/exports.d.ts +9 -6
- package/dist/agent/chat/exports.d.ts.map +1 -1
- package/dist/agent/chat/exports.js +11 -13
- package/dist/agent/chat/index.d.ts +6 -10
- package/dist/agent/chat/index.d.ts.map +1 -1
- package/dist/agent/chat/index.js +117 -196
- package/dist/agent/chat/models.d.ts +0 -2
- package/dist/agent/chat/models.d.ts.map +1 -1
- package/dist/agent/chat/models.js +12 -26
- package/dist/agent/chat/prompt/pw-utils-docs.d.ts +1 -1
- package/dist/agent/chat/prompt/pw-utils-docs.d.ts.map +1 -1
- package/dist/agent/chat/prompt/pw-utils-docs.js +52 -0
- package/dist/agent/chat/prompt/repo.d.ts.map +1 -1
- package/dist/agent/chat/prompt/repo.js +11 -22
- package/dist/agent/chat/prompt/test-case-def.d.ts +2 -0
- package/dist/agent/chat/prompt/test-case-def.d.ts.map +1 -0
- package/dist/agent/chat/prompt/test-case-def.js +44 -0
- package/dist/agent/chat/state.d.ts +8 -8
- package/dist/agent/chat/state.d.ts.map +1 -1
- package/dist/agent/chat/state.js +17 -47
- package/dist/agent/chat/utils.d.ts +4 -5
- package/dist/agent/chat/utils.d.ts.map +1 -1
- package/dist/agent/chat/utils.js +15 -9
- package/dist/agent/cli.d.ts +11 -0
- package/dist/agent/cli.d.ts.map +1 -0
- package/dist/agent/cli.js +213 -0
- package/dist/agent/code-review/executor/index.d.ts +5 -0
- package/dist/agent/code-review/executor/index.d.ts.map +1 -0
- package/dist/agent/code-review/executor/index.js +13 -0
- package/dist/agent/code-review/index.d.ts +12 -0
- package/dist/agent/code-review/index.d.ts.map +1 -0
- package/dist/agent/code-review/index.js +159 -0
- package/dist/agent/code-review/parser.d.ts +5 -0
- package/dist/agent/code-review/parser.d.ts.map +1 -0
- package/dist/agent/code-review/parser.js +70 -0
- package/dist/agent/code-review/types.d.ts +36 -0
- package/dist/agent/code-review/types.d.ts.map +1 -0
- package/dist/agent/code-review/types.js +13 -0
- package/dist/agent/cua/index.d.ts.map +1 -1
- package/dist/agent/cua/index.js +18 -2
- package/dist/agent/cua/model.d.ts.map +1 -1
- package/dist/agent/cua/model.js +4 -1
- package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +19 -0
- package/dist/agent/triage/index.d.ts +7 -0
- package/dist/agent/triage/index.d.ts.map +1 -0
- package/dist/agent/triage/index.js +103 -0
- package/dist/agent/video-analysis/executor/index.d.ts +5 -0
- package/dist/agent/video-analysis/executor/index.d.ts.map +1 -0
- package/dist/agent/video-analysis/executor/index.js +10 -0
- package/dist/agent/video-analysis/index.d.ts +7 -0
- package/dist/agent/video-analysis/index.d.ts.map +1 -0
- package/dist/agent/video-analysis/index.js +60 -0
- package/dist/artifacts/index.d.ts +1 -1
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +3 -1
- package/dist/artifacts/utils.d.ts.map +1 -1
- package/dist/bin/index.js +68 -23
- package/dist/constants/index.d.ts +14 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +33 -1
- package/dist/file/server.d.ts +1 -3
- package/dist/file/server.d.ts.map +1 -1
- package/dist/file/server.js +0 -13
- package/dist/file-info/adapters/file-system/index.d.ts.map +1 -1
- package/dist/file-info/adapters/file-system/reader.d.ts.map +1 -1
- package/dist/file-info/adapters/file-system/reader.js +8 -1
- package/dist/file-info/adapters/github/index.d.ts.map +1 -1
- package/dist/file-info/adapters/github/index.js +1 -2
- package/dist/file-info/adapters/github/reader.d.ts +4 -9
- package/dist/file-info/adapters/github/reader.d.ts.map +1 -1
- package/dist/file-info/adapters/github/reader.js +166 -134
- package/dist/index.d.ts.map +1 -1
- package/dist/tools/analyse-video/index.d.ts +5 -0
- package/dist/tools/analyse-video/index.d.ts.map +1 -0
- package/dist/tools/analyse-video/index.js +50 -0
- package/dist/tools/create-pull-request/index.d.ts.map +1 -0
- package/dist/tools/{definitions/commit-and-create-pr.js → create-pull-request/index.js} +28 -1
- package/dist/tools/create-pull-request/utils.d.ts +21 -0
- package/dist/tools/create-pull-request/utils.d.ts.map +1 -0
- package/dist/tools/create-pull-request/utils.js +83 -0
- package/dist/tools/definitions/{fetch-video-analysis.d.ts → analyse-video.d.ts} +17 -12
- package/dist/tools/definitions/analyse-video.d.ts.map +1 -0
- package/dist/tools/definitions/analyse-video.js +60 -0
- package/dist/tools/definitions/review-pull-request.d.ts +3 -0
- package/dist/tools/definitions/review-pull-request.d.ts.map +1 -0
- package/dist/tools/definitions/review-pull-request.js +16 -0
- package/dist/tools/definitions/str_replace_editor.d.ts +1 -0
- package/dist/tools/definitions/str_replace_editor.d.ts.map +1 -1
- package/dist/tools/definitions/str_replace_editor.js +4 -1
- package/dist/tools/definitions/test-gen-browser.d.ts +0 -3
- package/dist/tools/definitions/test-gen-browser.d.ts.map +1 -1
- package/dist/tools/definitions/test-gen-browser.js +33 -8
- package/dist/tools/delete-file/index.d.ts.map +1 -1
- package/dist/tools/delete-file/index.js +1 -19
- package/dist/tools/executor/base.d.ts +32 -0
- package/dist/tools/executor/base.d.ts.map +1 -0
- package/dist/tools/executor/base.js +114 -0
- package/dist/tools/executor/index.d.ts +3 -22
- package/dist/tools/executor/index.d.ts.map +1 -1
- package/dist/tools/executor/index.js +13 -92
- package/dist/tools/executor/utils/checkpoint.d.ts +1 -1
- package/dist/tools/executor/utils/checkpoint.d.ts.map +1 -1
- package/dist/tools/executor/utils/checkpoint.js +6 -2
- package/dist/tools/executor/utils/git.d.ts +2 -2
- package/dist/tools/executor/utils/git.d.ts.map +1 -1
- package/dist/tools/executor/utils/git.js +7 -3
- package/dist/tools/executor/utils/index.d.ts.map +1 -1
- package/dist/tools/executor/utils/index.js +1 -1
- package/dist/tools/fetch-session-diff/index.d.ts +3 -0
- package/dist/tools/fetch-session-diff/index.d.ts.map +1 -0
- package/dist/tools/fetch-session-diff/index.js +46 -0
- package/dist/tools/file-operations/create.d.ts.map +1 -1
- package/dist/tools/file-operations/create.js +1 -4
- package/dist/tools/file-operations/index.d.ts +2 -1
- package/dist/tools/file-operations/index.d.ts.map +1 -1
- package/dist/tools/file-operations/index.js +4 -1
- package/dist/tools/file-operations/insert.d.ts +1 -2
- package/dist/tools/file-operations/insert.d.ts.map +1 -1
- package/dist/tools/file-operations/insert.js +1 -4
- package/dist/tools/file-operations/replace.d.ts.map +1 -1
- package/dist/tools/file-operations/replace.js +1 -4
- package/dist/tools/grep/index.d.ts.map +1 -1
- package/dist/tools/grep/index.js +18 -11
- package/dist/tools/index.d.ts +28 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +52 -33
- package/dist/tools/merge-conflicts/index.d.ts.map +1 -1
- package/dist/tools/merge-conflicts/index.js +1 -1
- package/dist/tools/rename-file/index.js +1 -1
- package/dist/tools/review-pull-request/index.d.ts +3 -0
- package/dist/tools/review-pull-request/index.d.ts.map +1 -0
- package/dist/tools/review-pull-request/index.js +89 -0
- package/dist/tools/run-test.d.ts.map +1 -1
- package/dist/tools/run-test.js +25 -3
- package/dist/tools/test-gen-browser.d.ts.map +1 -1
- package/dist/tools/test-gen-browser.js +51 -47
- package/dist/tools/test-run-fetcher/index.d.ts.map +1 -1
- package/dist/tools/test-run-fetcher/index.js +4 -14
- package/dist/tools/utils/urls.d.ts +5 -0
- package/dist/tools/utils/urls.d.ts.map +1 -0
- package/dist/tools/utils/urls.js +19 -0
- package/dist/tools/view-failed-test-run-report/index.d.ts.map +1 -1
- package/dist/tools/view-failed-test-run-report/index.js +3 -15
- package/dist/utils/artifact-paths.d.ts +20 -0
- package/dist/utils/artifact-paths.d.ts.map +1 -0
- package/dist/utils/artifact-paths.js +16 -0
- package/dist/utils/dedup-image-fs.d.ts +2 -16
- package/dist/utils/dedup-image-fs.d.ts.map +1 -1
- package/dist/utils/dedup-image-fs.js +12 -16
- package/dist/utils/dedup-image.d.ts +1 -14
- package/dist/utils/dedup-image.d.ts.map +1 -1
- package/dist/utils/dedup-image.js +7 -62
- package/dist/{tools/fetch-video-analysis/local-ffmpeg-client.d.ts → utils/ffmpeg/index.d.ts} +9 -6
- package/dist/utils/ffmpeg/index.d.ts.map +1 -0
- package/dist/utils/ffmpeg/index.js +415 -0
- package/dist/utils/file.d.ts +1 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +45 -1
- package/dist/utils/find-threshold.d.ts +8 -0
- package/dist/utils/find-threshold.d.ts.map +1 -0
- package/dist/utils/find-threshold.js +55 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +24 -0
- package/dist/utils/model.d.ts +1 -1
- package/dist/utils/model.d.ts.map +1 -1
- package/dist/utils/model.js +7 -5
- package/dist/utils/repo-tree.d.ts +0 -1
- package/dist/utils/repo-tree.d.ts.map +1 -1
- package/dist/utils/repo-tree.js +2 -14
- package/dist/utils/slug.js +1 -1
- package/dist/video-core/agent-orchestrator.d.ts +14 -0
- package/dist/video-core/agent-orchestrator.d.ts.map +1 -0
- package/dist/video-core/agent-orchestrator.js +78 -0
- package/dist/video-core/analysis-server.d.ts +24 -0
- package/dist/video-core/analysis-server.d.ts.map +1 -0
- package/dist/video-core/analysis-server.js +398 -0
- package/dist/video-core/analysis-viewer.html +1374 -0
- package/dist/video-core/index.d.ts +44 -0
- package/dist/video-core/index.d.ts.map +1 -0
- package/dist/video-core/index.js +204 -0
- package/dist/video-core/model-limits.d.ts +4 -0
- package/dist/video-core/model-limits.d.ts.map +1 -0
- package/dist/video-core/model-limits.js +67 -0
- package/dist/video-core/storage-manager.d.ts +5 -0
- package/dist/video-core/storage-manager.d.ts.map +1 -0
- package/dist/video-core/storage-manager.js +55 -0
- package/dist/video-core/types.d.ts +13 -0
- package/dist/video-core/types.d.ts.map +1 -0
- package/dist/video-core/types.js +2 -0
- package/dist/video-core/utils.d.ts +25 -0
- package/dist/video-core/utils.d.ts.map +1 -0
- package/dist/video-core/utils.js +211 -0
- package/dist/video-core/xml-parser.d.ts +3 -0
- package/dist/video-core/xml-parser.d.ts.map +1 -0
- package/dist/video-core/xml-parser.js +27 -0
- package/package.json +5 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/agent/chat/prompt/index.d.ts +0 -5
- package/dist/agent/chat/prompt/index.d.ts.map +0 -1
- package/dist/agent/chat/prompt/index.js +0 -189
- package/dist/agent/chat/utils/tool-calls.d.ts +0 -21
- package/dist/agent/chat/utils/tool-calls.d.ts.map +0 -1
- package/dist/agent/chat/utils/tool-calls.js +0 -64
- package/dist/agent/code-review/prompt.d.ts +0 -2
- package/dist/agent/code-review/prompt.d.ts.map +0 -1
- package/dist/agent/code-review/prompt.js +0 -19
- package/dist/agent/diagnosis-agent/index.d.ts +0 -11
- package/dist/agent/diagnosis-agent/index.d.ts.map +0 -1
- package/dist/agent/diagnosis-agent/index.js +0 -88
- package/dist/agent/diagnosis-agent/strict-mode-violation.d.ts +0 -10
- package/dist/agent/diagnosis-agent/strict-mode-violation.d.ts.map +0 -1
- package/dist/agent/diagnosis-agent/strict-mode-violation.js +0 -30
- package/dist/tools/commit-and-create-pr/index.d.ts.map +0 -1
- package/dist/tools/commit-and-create-pr/index.js +0 -83
- package/dist/tools/definitions/commit-and-create-pr.d.ts +0 -3
- package/dist/tools/definitions/commit-and-create-pr.d.ts.map +0 -1
- package/dist/tools/definitions/fetch-video-analysis.d.ts.map +0 -1
- package/dist/tools/definitions/fetch-video-analysis.js +0 -61
- package/dist/tools/fetch-video-analysis/index.d.ts +0 -5
- package/dist/tools/fetch-video-analysis/index.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/index.js +0 -138
- package/dist/tools/fetch-video-analysis/local-ffmpeg-client.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/local-ffmpeg-client.js +0 -247
- package/dist/tools/fetch-video-analysis/open-ai.d.ts +0 -6
- package/dist/tools/fetch-video-analysis/open-ai.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/open-ai.js +0 -37
- package/dist/tools/fetch-video-analysis/utils.d.ts +0 -13
- package/dist/tools/fetch-video-analysis/utils.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/utils.js +0 -98
- package/dist/tools/fetch-video-analysis/video-analysis.d.ts +0 -7
- package/dist/tools/fetch-video-analysis/video-analysis.d.ts.map +0 -1
- package/dist/tools/fetch-video-analysis/video-analysis.js +0 -54
- package/dist/tools/file-operations/shared/git-helper.d.ts +0 -4
- package/dist/tools/file-operations/shared/git-helper.d.ts.map +0 -1
- package/dist/tools/file-operations/shared/git-helper.js +0 -29
- package/eslint.config.mjs +0 -43
- /package/dist/tools/{commit-and-create-pr → create-pull-request}/index.d.ts +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { VideoAnalysisParams } from "@empiricalrun/shared-types";
|
|
2
|
+
export declare function parseVideoAnalysisParams(paramsString: string): VideoAnalysisParams;
|
|
3
|
+
export declare function runVideoAnalysisForCli({ url, params, startServer, }: {
|
|
4
|
+
url: string;
|
|
5
|
+
params: string;
|
|
6
|
+
startServer?: boolean;
|
|
7
|
+
}): Promise<import("./analysis-server").AnalysisServerHandle>;
|
|
8
|
+
export declare function runVideoAnalysis({ videoUrl, videoUrlHash, params, featureFlags, workingDirectory, skipUpload, }: {
|
|
9
|
+
videoUrl: string;
|
|
10
|
+
videoUrlHash: string;
|
|
11
|
+
params: VideoAnalysisParams;
|
|
12
|
+
featureFlags: string[];
|
|
13
|
+
workingDirectory: string;
|
|
14
|
+
skipUpload?: boolean;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
result: {
|
|
17
|
+
total_extracted_frames: number;
|
|
18
|
+
video_duration: number;
|
|
19
|
+
unique_frames_count: number;
|
|
20
|
+
video_url: string;
|
|
21
|
+
analysis: string;
|
|
22
|
+
analysis_id: string;
|
|
23
|
+
params: VideoAnalysisParams;
|
|
24
|
+
};
|
|
25
|
+
unique_frames: import("@empiricalrun/shared-types").UniqueFrameInfos[];
|
|
26
|
+
interleaved_tool_result: import("@empiricalrun/shared-types").ToolResultPart[];
|
|
27
|
+
chat_messages: import("@empiricalrun/shared-types").CanonicalMessage[];
|
|
28
|
+
isError: boolean;
|
|
29
|
+
} | {
|
|
30
|
+
result: {
|
|
31
|
+
total_extracted_frames: number;
|
|
32
|
+
video_duration: number;
|
|
33
|
+
unique_frames_count: number;
|
|
34
|
+
video_url: string;
|
|
35
|
+
analysis: string;
|
|
36
|
+
analysis_id: string;
|
|
37
|
+
params: VideoAnalysisParams;
|
|
38
|
+
};
|
|
39
|
+
interleaved_tool_result: never[];
|
|
40
|
+
chat_messages: never[];
|
|
41
|
+
isError: boolean;
|
|
42
|
+
unique_frames?: undefined;
|
|
43
|
+
}>;
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/video-core/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAa7E,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,GACnB,mBAAmB,CAoCrB;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,GAAG,EACH,MAAM,EACN,WAAkB,GACnB,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,6DAuFA;AAED,wBAAsB,gBAAgB,CAAC,EACrC,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,UAAU,GACX,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGA"}
|
|
@@ -0,0 +1,204 @@
|
|
|
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.parseVideoAnalysisParams = parseVideoAnalysisParams;
|
|
7
|
+
exports.runVideoAnalysisForCli = runVideoAnalysisForCli;
|
|
8
|
+
exports.runVideoAnalysis = runVideoAnalysis;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const constants_1 = require("../constants");
|
|
12
|
+
const ffmpeg_1 = require("../utils/ffmpeg");
|
|
13
|
+
const hash_1 = require("../utils/hash");
|
|
14
|
+
const agent_orchestrator_1 = require("./agent-orchestrator");
|
|
15
|
+
const model_limits_1 = require("./model-limits");
|
|
16
|
+
const storage_manager_1 = require("./storage-manager");
|
|
17
|
+
const utils_1 = require("./utils");
|
|
18
|
+
function parseVideoAnalysisParams(paramsString) {
|
|
19
|
+
const defaults = {
|
|
20
|
+
model: constants_1.VIDEO_ANALYSIS.DEFAULT_MODEL,
|
|
21
|
+
fps: constants_1.VIDEO_ANALYSIS.DEFAULT_FPS,
|
|
22
|
+
threshold: constants_1.VIDEO_ANALYSIS.DEFAULT_THRESHOLD,
|
|
23
|
+
};
|
|
24
|
+
if (!paramsString?.trim())
|
|
25
|
+
return defaults;
|
|
26
|
+
const result = { ...defaults };
|
|
27
|
+
for (const chunk of paramsString.split(",")) {
|
|
28
|
+
const [key, value] = chunk.split("=").map((s) => s.trim());
|
|
29
|
+
if (!key || !value)
|
|
30
|
+
continue;
|
|
31
|
+
switch (key.toLowerCase()) {
|
|
32
|
+
case "fps":
|
|
33
|
+
case "threshold": {
|
|
34
|
+
const num = Number(value);
|
|
35
|
+
if (Number.isFinite(num))
|
|
36
|
+
result[key] = num;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "model":
|
|
40
|
+
result.model = value;
|
|
41
|
+
break;
|
|
42
|
+
case "starttime":
|
|
43
|
+
case "start_time":
|
|
44
|
+
result.startTime = Number(value);
|
|
45
|
+
break;
|
|
46
|
+
case "duration":
|
|
47
|
+
result.duration = Number(value);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
async function runVideoAnalysisForCli({ url, params, startServer = true, }) {
|
|
54
|
+
console.log("🎬 Starting video analysis...");
|
|
55
|
+
console.log(`📹 Video URL: ${url}`);
|
|
56
|
+
console.log(`⚙️ Parameters: ${params || "default"}`);
|
|
57
|
+
const videoAnalysisParams = parseVideoAnalysisParams(params);
|
|
58
|
+
const videoUrlHash = (0, hash_1.createHashBasedOnParams)(url, videoAnalysisParams);
|
|
59
|
+
const WORKING_DIR = node_path_1.default.join("video-analysis", videoUrlHash);
|
|
60
|
+
let result;
|
|
61
|
+
const startTime = performance.now();
|
|
62
|
+
try {
|
|
63
|
+
const { result: analysisResult, unique_frames, interleaved_tool_result, chat_messages, } = await runVideoAnalysis({
|
|
64
|
+
videoUrl: url,
|
|
65
|
+
videoUrlHash,
|
|
66
|
+
params: videoAnalysisParams,
|
|
67
|
+
featureFlags: [],
|
|
68
|
+
workingDirectory: WORKING_DIR,
|
|
69
|
+
skipUpload: true,
|
|
70
|
+
});
|
|
71
|
+
result = {
|
|
72
|
+
...analysisResult,
|
|
73
|
+
unique_frames: unique_frames,
|
|
74
|
+
interleaved_tool_result: interleaved_tool_result,
|
|
75
|
+
chat_messages: chat_messages,
|
|
76
|
+
};
|
|
77
|
+
const endTime = performance.now();
|
|
78
|
+
const durationMs = endTime - startTime;
|
|
79
|
+
console.log(`[Performance] Video analysis completed in ${durationMs.toFixed(2)}ms (${(durationMs / 1000).toFixed(2)}s)`);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error("Error during video analysis:", error);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
console.log("✅ Video analysis completed!");
|
|
86
|
+
console.log(`📊 Found ${result.unique_frames_count} unique frames`);
|
|
87
|
+
if (startServer) {
|
|
88
|
+
const resultFileName = `analysis-result.json`;
|
|
89
|
+
await node_fs_1.default.promises.mkdir(WORKING_DIR, { recursive: true });
|
|
90
|
+
const resultFilePath = node_path_1.default.join(WORKING_DIR, resultFileName);
|
|
91
|
+
await node_fs_1.default.promises.writeFile(resultFilePath, JSON.stringify({ ...result, workingDir: WORKING_DIR }, null, 2));
|
|
92
|
+
console.log(`💾 Analysis result saved to: ${resultFilePath}`);
|
|
93
|
+
console.log("🚀 Starting analysis viewer server...");
|
|
94
|
+
// Dynamic import to avoid circular dependency
|
|
95
|
+
const { startAnalysisServer } = await import("./analysis-server.js");
|
|
96
|
+
const handle = await startAnalysisServer("video-analysis");
|
|
97
|
+
console.log(`🌐 Analysis viewer available at: ${handle.url}`);
|
|
98
|
+
console.log("📋 Press Ctrl+C to stop the server");
|
|
99
|
+
let isShuttingDown = false;
|
|
100
|
+
const gracefulShutdown = async (signal) => {
|
|
101
|
+
if (isShuttingDown)
|
|
102
|
+
return;
|
|
103
|
+
isShuttingDown = true;
|
|
104
|
+
console.log(`\n🛑 Received ${signal}, shutting down server...`);
|
|
105
|
+
try {
|
|
106
|
+
await handle.close();
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error("Error closing server:", error);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
process.once("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
115
|
+
process.once("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
116
|
+
return handle;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log("🚀 Analysis viewer server not started. Results available in the CLI.");
|
|
120
|
+
console.log(result);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function runVideoAnalysis({ videoUrl, videoUrlHash, params, featureFlags, workingDirectory, skipUpload, }) {
|
|
125
|
+
try {
|
|
126
|
+
const ffmpegClient = new ffmpeg_1.LocalFFmpegClient();
|
|
127
|
+
const extractionResult = await ffmpegClient.extractVideoFrames(videoUrl, videoUrlHash, workingDirectory, {
|
|
128
|
+
fps: params.fps,
|
|
129
|
+
threshold: params.threshold,
|
|
130
|
+
startTime: params.startTime,
|
|
131
|
+
duration: params.duration,
|
|
132
|
+
});
|
|
133
|
+
const { totalFramesCount, uniqueFrameInfos, videoDurationSeconds } = extractionResult;
|
|
134
|
+
await (0, storage_manager_1.uploadFramesToR2)(videoUrlHash, uniqueFrameInfos);
|
|
135
|
+
const batchSize = await (0, model_limits_1.calculateOptimalBatchSize)(params.model, uniqueFrameInfos);
|
|
136
|
+
const frameBatch = uniqueFrameInfos
|
|
137
|
+
.map((f) => {
|
|
138
|
+
return {
|
|
139
|
+
name: f.fileName,
|
|
140
|
+
contentType: "image/png",
|
|
141
|
+
url: f.url,
|
|
142
|
+
};
|
|
143
|
+
})
|
|
144
|
+
.slice(0, batchSize);
|
|
145
|
+
const { analysis, allMessages, parsedXml } = await (0, agent_orchestrator_1.orchestrateVideoAnalysis)({
|
|
146
|
+
selectedModel: params.model,
|
|
147
|
+
featureFlags,
|
|
148
|
+
workingDirectory,
|
|
149
|
+
frameBatch,
|
|
150
|
+
});
|
|
151
|
+
const isTruncated = frameBatch.length !== batchSize;
|
|
152
|
+
const finalAnalysis = isTruncated
|
|
153
|
+
? `NOTE: the unique frames to be processed were truncated to ${batchSize} due to limitations on the number of frames that can be processed in a single batch. ${analysis}`
|
|
154
|
+
: analysis;
|
|
155
|
+
const interleavedResults = (0, utils_1.createInterleavedResults)(parsedXml, uniqueFrameInfos);
|
|
156
|
+
const videoAnalysisSummary = {
|
|
157
|
+
total_extracted_frames: totalFramesCount,
|
|
158
|
+
video_duration: videoDurationSeconds,
|
|
159
|
+
unique_frames_count: uniqueFrameInfos.length,
|
|
160
|
+
video_url: videoUrl,
|
|
161
|
+
analysis: finalAnalysis,
|
|
162
|
+
analysis_id: videoUrlHash,
|
|
163
|
+
params,
|
|
164
|
+
interleaved_tool_result: interleavedResults,
|
|
165
|
+
};
|
|
166
|
+
if (!skipUpload) {
|
|
167
|
+
await (0, storage_manager_1.uploadSummaryToR2)(videoAnalysisSummary);
|
|
168
|
+
await (0, storage_manager_1.uploadChatStateToR2)(allMessages, videoUrlHash);
|
|
169
|
+
}
|
|
170
|
+
const toolResult = {
|
|
171
|
+
total_extracted_frames: totalFramesCount,
|
|
172
|
+
video_duration: videoDurationSeconds,
|
|
173
|
+
unique_frames_count: uniqueFrameInfos.length,
|
|
174
|
+
video_url: videoUrl,
|
|
175
|
+
analysis: finalAnalysis,
|
|
176
|
+
analysis_id: videoUrlHash,
|
|
177
|
+
params,
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
result: toolResult,
|
|
181
|
+
unique_frames: uniqueFrameInfos,
|
|
182
|
+
interleaved_tool_result: interleavedResults,
|
|
183
|
+
chat_messages: allMessages,
|
|
184
|
+
isError: false,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
189
|
+
return {
|
|
190
|
+
result: {
|
|
191
|
+
total_extracted_frames: 0,
|
|
192
|
+
video_duration: 0,
|
|
193
|
+
unique_frames_count: 0,
|
|
194
|
+
video_url: videoUrl,
|
|
195
|
+
analysis: `Error during video analysis: ${errorMessage}`,
|
|
196
|
+
analysis_id: videoUrlHash,
|
|
197
|
+
params,
|
|
198
|
+
},
|
|
199
|
+
interleaved_tool_result: [],
|
|
200
|
+
chat_messages: [],
|
|
201
|
+
isError: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SupportedChatModels, UniqueFrameInfos } from "@empiricalrun/shared-types";
|
|
2
|
+
export declare function calculateActualBatchSize(uniqueFrameInfos: UniqueFrameInfos[]): Promise<number>;
|
|
3
|
+
export declare function calculateOptimalBatchSize(model: SupportedChatModels, uniqueFrameInfos: UniqueFrameInfos[]): Promise<number>;
|
|
4
|
+
//# sourceMappingURL=model-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-limits.d.ts","sourceRoot":"","sources":["../../src/video-core/model-limits.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AAgCpC,wBAAsB,wBAAwB,CAC5C,gBAAgB,EAAE,gBAAgB,EAAE,GACnC,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,mBAAmB,EAC1B,gBAAgB,EAAE,gBAAgB,EAAE,GACnC,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
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.calculateActualBatchSize = calculateActualBatchSize;
|
|
7
|
+
exports.calculateOptimalBatchSize = calculateOptimalBatchSize;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const constants_1 = require("../constants");
|
|
10
|
+
function isClaudeModel(model) {
|
|
11
|
+
return model.includes("claude") || model.includes("sonnet");
|
|
12
|
+
}
|
|
13
|
+
function isOpenAIModel(model) {
|
|
14
|
+
return model.includes("gpt") || model.includes("openai");
|
|
15
|
+
}
|
|
16
|
+
function getModelLimits(model) {
|
|
17
|
+
if (isClaudeModel(model)) {
|
|
18
|
+
return {
|
|
19
|
+
maxImages: constants_1.VIDEO_ANALYSIS.CLAUDE_MAX_IMAGES_PER_BATCH,
|
|
20
|
+
maxSizeMB: constants_1.VIDEO_ANALYSIS.CLAUDE_MAX_REQUEST_SIZE_MB,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (isOpenAIModel(model)) {
|
|
24
|
+
return {
|
|
25
|
+
maxImages: constants_1.VIDEO_ANALYSIS.OPENAI_MAX_IMAGES_PER_BATCH,
|
|
26
|
+
maxSizeMB: constants_1.VIDEO_ANALYSIS.OPENAI_MAX_REQUEST_SIZE_MB,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
maxImages: 20,
|
|
31
|
+
maxSizeMB: 10,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function calculateActualBatchSize(uniqueFrameInfos) {
|
|
35
|
+
try {
|
|
36
|
+
const sizes = await Promise.all(uniqueFrameInfos.map(async (frame, index) => {
|
|
37
|
+
const pathToUse = frame.path;
|
|
38
|
+
if (!pathToUse) {
|
|
39
|
+
throw new Error(`Frame ${index} has no path: ${JSON.stringify(frame)}`);
|
|
40
|
+
}
|
|
41
|
+
const stats = await node_fs_1.default.promises.stat(pathToUse);
|
|
42
|
+
return stats.size;
|
|
43
|
+
}));
|
|
44
|
+
const totalBytes = sizes.reduce((sum, size) => sum + size, 0);
|
|
45
|
+
const totalMB = totalBytes / (1024 * 1024);
|
|
46
|
+
return totalMB;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return uniqueFrameInfos.length * constants_1.VIDEO_ANALYSIS.DEFAULT_FRAME_SIZE_MB;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function calculateOptimalBatchSize(model, uniqueFrameInfos) {
|
|
53
|
+
if (!uniqueFrameInfos || uniqueFrameInfos.length === 0) {
|
|
54
|
+
throw new Error("framePaths are required to calculate the optimal batch size");
|
|
55
|
+
}
|
|
56
|
+
const limits = getModelLimits(model);
|
|
57
|
+
let batchSize = Math.min(limits.maxImages, uniqueFrameInfos.length);
|
|
58
|
+
while (batchSize > 0) {
|
|
59
|
+
const frameBatch = uniqueFrameInfos.slice(0, batchSize);
|
|
60
|
+
const actualBatchSizeMB = await calculateActualBatchSize(frameBatch);
|
|
61
|
+
if (actualBatchSizeMB <= limits.maxSizeMB) {
|
|
62
|
+
return batchSize;
|
|
63
|
+
}
|
|
64
|
+
batchSize = Math.floor(batchSize * constants_1.VIDEO_ANALYSIS.BATCH_SIZE_REDUCTION_FACTOR);
|
|
65
|
+
}
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CanonicalMessage, UniqueFrameInfos, VideoAnalysisSummary } from "@empiricalrun/shared-types";
|
|
2
|
+
export declare const uploadFramesToR2: (videoUrlHash: string, frames: UniqueFrameInfos[]) => Promise<void>;
|
|
3
|
+
export declare const uploadSummaryToR2: (videoInfo: VideoAnalysisSummary) => Promise<void>;
|
|
4
|
+
export declare function uploadChatStateToR2(allMessages: CanonicalMessage[], videoUrlHash: string): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=storage-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-manager.d.ts","sourceRoot":"","sources":["../../src/video-core/storage-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,4BAA4B,CAAC;AAoCpC,eAAO,MAAM,gBAAgB,GAC3B,cAAc,MAAM,EACpB,QAAQ,gBAAgB,EAAE,KACzB,OAAO,CAAC,IAAI,CAQd,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,oBAAoB,KAC9B,OAAO,CAAC,IAAI,CAWd,CAAC;AAEF,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uploadSummaryToR2 = exports.uploadFramesToR2 = void 0;
|
|
4
|
+
exports.uploadChatStateToR2 = uploadChatStateToR2;
|
|
5
|
+
const r2_uploader_1 = require("@empiricalrun/r2-uploader");
|
|
6
|
+
const uploadToR2 = async (files, videoUrlHash) => {
|
|
7
|
+
const label = `video-analysis-upload:${videoUrlHash}`;
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
console.log(`[${label}] preparing ${files.length} files for upload...`);
|
|
10
|
+
console.log(`[${label}] starting upload of ${files.length} files to video-analysis/${videoUrlHash}/...`);
|
|
11
|
+
const interval = setInterval(() => {
|
|
12
|
+
const secs = Math.round((Date.now() - start) / 1000);
|
|
13
|
+
console.log(`[${label}] uploading... ${secs}s elapsed`);
|
|
14
|
+
}, 1000);
|
|
15
|
+
try {
|
|
16
|
+
await (0, r2_uploader_1.uploadInMemoryFiles)({
|
|
17
|
+
files,
|
|
18
|
+
destinationDir: videoUrlHash,
|
|
19
|
+
uploadBucket: "video-analysis",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
clearInterval(interval);
|
|
24
|
+
const secs = Math.round((Date.now() - start) / 1000);
|
|
25
|
+
console.log(`[${label}] upload complete in ${secs}s`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const uploadFramesToR2 = async (videoUrlHash, frames) => {
|
|
29
|
+
const frameFiles = frames.map((f) => ({
|
|
30
|
+
buffer: Buffer.from(f.base64, "base64"),
|
|
31
|
+
fileName: f.fileName,
|
|
32
|
+
mimeType: "image/png",
|
|
33
|
+
}));
|
|
34
|
+
await uploadToR2(frameFiles, videoUrlHash);
|
|
35
|
+
};
|
|
36
|
+
exports.uploadFramesToR2 = uploadFramesToR2;
|
|
37
|
+
const uploadSummaryToR2 = async (videoInfo) => {
|
|
38
|
+
await uploadToR2([
|
|
39
|
+
{
|
|
40
|
+
buffer: Buffer.from(JSON.stringify(videoInfo, null, 2)),
|
|
41
|
+
fileName: "summary.json",
|
|
42
|
+
mimeType: "application/json",
|
|
43
|
+
},
|
|
44
|
+
], videoInfo.analysis_id);
|
|
45
|
+
};
|
|
46
|
+
exports.uploadSummaryToR2 = uploadSummaryToR2;
|
|
47
|
+
async function uploadChatStateToR2(allMessages, videoUrlHash) {
|
|
48
|
+
await uploadToR2([
|
|
49
|
+
{
|
|
50
|
+
buffer: Buffer.from(JSON.stringify(allMessages)),
|
|
51
|
+
fileName: "chat-state.json",
|
|
52
|
+
mimeType: "application/json",
|
|
53
|
+
},
|
|
54
|
+
], videoUrlHash);
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { VideoAnalysisToolResponse } from "@empiricalrun/shared-types";
|
|
2
|
+
export interface AnalysisProcessingContext {
|
|
3
|
+
rawAnalysis: string;
|
|
4
|
+
artifactsPath: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ProcessedAnalysis {
|
|
7
|
+
parsedXml: VideoAnalysisToolResponse[];
|
|
8
|
+
keyFrameImagesMap: Map<string, {
|
|
9
|
+
type: "image/png";
|
|
10
|
+
base64Data: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/video-core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,yBAAyB,EAAE,CAAC;IACvC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ToolResultPart, UniqueFrameInfos, VideoAnalysisToolResponse } from "@empiricalrun/shared-types";
|
|
2
|
+
export declare const getR2BaseUrlByHash: (videoUrlHash: string) => string;
|
|
3
|
+
export declare const parseXmlSummaryToJson: (xmlContent: string) => Array<VideoAnalysisToolResponse>;
|
|
4
|
+
export declare function loadKeyFrameImages(keyFrames: string[], extractionArtifactsDir: string): Promise<Map<string, {
|
|
5
|
+
type: "image/png";
|
|
6
|
+
base64Data: string;
|
|
7
|
+
}>>;
|
|
8
|
+
export declare function createInterleavedResults(parsedAnalysis: VideoAnalysisToolResponse[], uniqueFrameInfos: UniqueFrameInfos[]): ToolResultPart[];
|
|
9
|
+
export interface VideoValidationResult {
|
|
10
|
+
isValid: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function validateVideoAccess(videoUrl: string): Promise<VideoValidationResult>;
|
|
14
|
+
export declare function downloadVideo(videoUrl: string, outputPath: string): Promise<void>;
|
|
15
|
+
export declare function convertFramesForUpload(keyFrameImagesMap: Map<string, {
|
|
16
|
+
type: "image/png";
|
|
17
|
+
base64Data: string;
|
|
18
|
+
}>): {
|
|
19
|
+
metadata: {
|
|
20
|
+
index: number;
|
|
21
|
+
path: string;
|
|
22
|
+
};
|
|
23
|
+
image: string;
|
|
24
|
+
}[];
|
|
25
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/video-core/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,4BAA4B,CAAC;AAIpC,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,WAEtD,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,YAAY,MAAM,KACjB,KAAK,CAAC,yBAAyB,CAyCjC,CAAC;AA8DF,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EAAE,EACnB,sBAAsB,EAAE,MAAM,GAC7B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAkBjE;AAED,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,yBAAyB,EAAE,EAC3C,gBAAgB,EAAE,gBAAgB,EAAE,GACnC,cAAc,EAAE,CAwClB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAgBhC;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,GACxE;IACD,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;CACf,EAAE,CAiBF"}
|
|
@@ -0,0 +1,211 @@
|
|
|
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.parseXmlSummaryToJson = exports.getR2BaseUrlByHash = void 0;
|
|
7
|
+
exports.loadKeyFrameImages = loadKeyFrameImages;
|
|
8
|
+
exports.createInterleavedResults = createInterleavedResults;
|
|
9
|
+
exports.validateVideoAccess = validateVideoAccess;
|
|
10
|
+
exports.downloadVideo = downloadVideo;
|
|
11
|
+
exports.convertFramesForUpload = convertFramesForUpload;
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const getR2BaseUrlByHash = (videoUrlHash) => {
|
|
15
|
+
return `https://video-analysis.empirical.run/${videoUrlHash}/`;
|
|
16
|
+
};
|
|
17
|
+
exports.getR2BaseUrlByHash = getR2BaseUrlByHash;
|
|
18
|
+
const parseXmlSummaryToJson = (xmlContent) => {
|
|
19
|
+
const sections = [];
|
|
20
|
+
try {
|
|
21
|
+
// Find the summary block first to limit parsing scope
|
|
22
|
+
const summaryMatch = xmlContent.match(/<summary>([\s\S]*?)<\/summary>/);
|
|
23
|
+
if (!summaryMatch) {
|
|
24
|
+
return sections;
|
|
25
|
+
}
|
|
26
|
+
const summaryContent = summaryMatch[1];
|
|
27
|
+
if (!summaryContent) {
|
|
28
|
+
return sections;
|
|
29
|
+
}
|
|
30
|
+
// Extract all section blocks using matchAll to avoid infinite loop
|
|
31
|
+
const sectionMatches = Array.from(summaryContent.matchAll(/<section>([\s\S]*?)<\/section>/g) || []);
|
|
32
|
+
for (const sectionMatch of sectionMatches) {
|
|
33
|
+
const sectionContent = sectionMatch?.[1];
|
|
34
|
+
if (!sectionContent)
|
|
35
|
+
continue;
|
|
36
|
+
const keyFrameMatch = sectionContent.match(/<key_frame>(.*?)<\/key_frame>/);
|
|
37
|
+
const descriptionMatch = sectionContent.match(/<description>(.*?)<\/description>/);
|
|
38
|
+
if (keyFrameMatch?.[1] && descriptionMatch?.[1]) {
|
|
39
|
+
sections.push({
|
|
40
|
+
key_frame: keyFrameMatch[1].trim(),
|
|
41
|
+
description: descriptionMatch[1].trim(),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) { }
|
|
47
|
+
return sections;
|
|
48
|
+
};
|
|
49
|
+
exports.parseXmlSummaryToJson = parseXmlSummaryToJson;
|
|
50
|
+
async function fileExists(filePath) {
|
|
51
|
+
return fs_1.promises
|
|
52
|
+
.access(filePath)
|
|
53
|
+
.then(() => true)
|
|
54
|
+
.catch(() => false);
|
|
55
|
+
}
|
|
56
|
+
// Try unique_frames directory first, then consolidated_frames as fallback
|
|
57
|
+
async function findFramePath(frameId, baseDir) {
|
|
58
|
+
const uniqueFramePath = path_1.default.join(baseDir, "unique_frames", `${frameId}.png`);
|
|
59
|
+
const consolidatedFramePath = path_1.default.join(baseDir, "consolidated_frames", `${frameId}.png`);
|
|
60
|
+
if (await fileExists(uniqueFramePath)) {
|
|
61
|
+
return uniqueFramePath;
|
|
62
|
+
}
|
|
63
|
+
if (await fileExists(consolidatedFramePath)) {
|
|
64
|
+
return consolidatedFramePath;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
async function loadFrameImage(framePath) {
|
|
69
|
+
const imageBuffer = await fs_1.promises.readFile(framePath);
|
|
70
|
+
const base64Data = imageBuffer.toString("base64");
|
|
71
|
+
return {
|
|
72
|
+
type: "image/png",
|
|
73
|
+
base64Data: base64Data,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function loadSingleFrame(frameId, extractionArtifactsDir) {
|
|
77
|
+
try {
|
|
78
|
+
const framePath = await findFramePath(frameId, extractionArtifactsDir);
|
|
79
|
+
if (!framePath) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const imageData = await loadFrameImage(framePath);
|
|
83
|
+
return {
|
|
84
|
+
frameId: frameId,
|
|
85
|
+
imageData,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function loadKeyFrameImages(keyFrames, extractionArtifactsDir) {
|
|
93
|
+
const frameImageParts = await Promise.all(keyFrames.map((frameId) => loadSingleFrame(frameId, extractionArtifactsDir)));
|
|
94
|
+
const frameImageMap = new Map();
|
|
95
|
+
frameImageParts
|
|
96
|
+
.filter((part) => part !== null)
|
|
97
|
+
.forEach(({ frameId, imageData }) => {
|
|
98
|
+
frameImageMap.set(frameId, imageData);
|
|
99
|
+
});
|
|
100
|
+
return frameImageMap;
|
|
101
|
+
}
|
|
102
|
+
function createInterleavedResults(parsedAnalysis, uniqueFrameInfos) {
|
|
103
|
+
const interleavedResults = [];
|
|
104
|
+
if (parsedAnalysis && Array.isArray(parsedAnalysis)) {
|
|
105
|
+
for (const section of parsedAnalysis) {
|
|
106
|
+
// Add section text
|
|
107
|
+
interleavedResults.push({
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify(section),
|
|
110
|
+
});
|
|
111
|
+
// Add corresponding frame image if available
|
|
112
|
+
if (section.key_frame && uniqueFrameInfos.length > 0) {
|
|
113
|
+
const sectionFrameId = section.key_frame;
|
|
114
|
+
// Extract frame index from frameId (e.g., "frame_6" -> 6)
|
|
115
|
+
const frameNumberMatch = sectionFrameId.match(/frame_(\d+)/);
|
|
116
|
+
const frameIndex = frameNumberMatch && frameNumberMatch[1]
|
|
117
|
+
? parseInt(frameNumberMatch[1], 10)
|
|
118
|
+
: null;
|
|
119
|
+
if (frameIndex !== null) {
|
|
120
|
+
// Find the corresponding uploaded frame by index
|
|
121
|
+
const uploadedFrame = uniqueFrameInfos.find((frame) => frame.index === frameIndex);
|
|
122
|
+
if (uploadedFrame) {
|
|
123
|
+
interleavedResults.push({
|
|
124
|
+
type: "image/png",
|
|
125
|
+
url: uploadedFrame.url,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return interleavedResults;
|
|
133
|
+
}
|
|
134
|
+
async function validateVideoAccess(videoUrl) {
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(videoUrl, { method: "HEAD" });
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
return {
|
|
139
|
+
isValid: false,
|
|
140
|
+
error: `Failed to access video at ${videoUrl}: ${response.statusText}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { isValid: true };
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
isValid: false,
|
|
148
|
+
error: `Network error accessing video: ${error instanceof Error ? error.message : String(error)}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function downloadVideo(videoUrl, outputPath) {
|
|
153
|
+
if (await fileExists(outputPath)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const response = await fetch(videoUrl);
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error(`Failed to download video: ${response.statusText}`);
|
|
159
|
+
}
|
|
160
|
+
const contentLength = response.headers.get("content-length");
|
|
161
|
+
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
|
|
162
|
+
if (!response.body) {
|
|
163
|
+
throw new Error("Response body is null");
|
|
164
|
+
}
|
|
165
|
+
const reader = response.body.getReader();
|
|
166
|
+
const chunks = [];
|
|
167
|
+
let downloadedSize = 0;
|
|
168
|
+
const startTime = Date.now();
|
|
169
|
+
while (true) {
|
|
170
|
+
const { done, value } = await reader.read();
|
|
171
|
+
if (done)
|
|
172
|
+
break;
|
|
173
|
+
chunks.push(value);
|
|
174
|
+
downloadedSize += value.length;
|
|
175
|
+
const elapsedSeconds = (Date.now() - startTime) / 1000;
|
|
176
|
+
const speed = elapsedSeconds > 0 ? downloadedSize / elapsedSeconds : 0;
|
|
177
|
+
const speedMBps = (speed / 1024 / 1024).toFixed(1);
|
|
178
|
+
if (totalSize > 0) {
|
|
179
|
+
const percentage = Math.round((downloadedSize / totalSize) * 100);
|
|
180
|
+
const progressBar = "█".repeat(Math.floor(percentage / 5)) +
|
|
181
|
+
"░".repeat(20 - Math.floor(percentage / 5));
|
|
182
|
+
process.stdout.write(`\r[${progressBar}] ${percentage}% (${(downloadedSize / 1024 / 1024).toFixed(1)} MB) ${speedMBps} MB/s`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
process.stdout.write(`\rDownloaded: ${(downloadedSize / 1024 / 1024).toFixed(1)} MB @ ${speedMBps} MB/s`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const buffer = new Uint8Array(downloadedSize);
|
|
189
|
+
let offset = 0;
|
|
190
|
+
for (const chunk of chunks) {
|
|
191
|
+
buffer.set(chunk, offset);
|
|
192
|
+
offset += chunk.length;
|
|
193
|
+
}
|
|
194
|
+
await fs_1.promises.writeFile(outputPath, buffer);
|
|
195
|
+
}
|
|
196
|
+
function convertFramesForUpload(keyFrameImagesMap) {
|
|
197
|
+
return Array.from(keyFrameImagesMap.entries()).map(([frameId, imageData]) => {
|
|
198
|
+
// Extract frame index from frameId (e.g., "frame_000006" -> 6)
|
|
199
|
+
const frameNumberMatch = frameId.match(/frame_(\d+)/);
|
|
200
|
+
const frameIndex = frameNumberMatch && frameNumberMatch[1]
|
|
201
|
+
? parseInt(frameNumberMatch[1], 10)
|
|
202
|
+
: 0;
|
|
203
|
+
return {
|
|
204
|
+
metadata: {
|
|
205
|
+
index: frameIndex,
|
|
206
|
+
path: `${frameId}.png`,
|
|
207
|
+
},
|
|
208
|
+
image: imageData.base64Data,
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml-parser.d.ts","sourceRoot":"","sources":["../../src/video-core/xml-parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAmBvE,wBAAsB,4BAA4B,CAAC,EACjD,WAAW,EACX,aAAa,GACd,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiBxD"}
|