@larkup/tool-video-intelligence 0.2.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 (54) hide show
  1. package/.env.example +150 -0
  2. package/LICENSE +176 -0
  3. package/README.md +281 -0
  4. package/compose.gpu.yaml +14 -0
  5. package/compose.yaml +71 -0
  6. package/dist/agent.d.ts +131 -0
  7. package/dist/agent.js +2087 -0
  8. package/dist/brief.d.ts +2 -0
  9. package/dist/brief.js +37 -0
  10. package/dist/client.d.ts +46 -0
  11. package/dist/client.js +139 -0
  12. package/dist/contracts.d.ts +331 -0
  13. package/dist/contracts.js +1 -0
  14. package/dist/index.d.ts +87 -0
  15. package/dist/index.js +391 -0
  16. package/dist/runtime.d.ts +96 -0
  17. package/dist/runtime.js +592 -0
  18. package/dist/ui.d.ts +82 -0
  19. package/dist/ui.js +87 -0
  20. package/package.json +84 -0
  21. package/runtime/Dockerfile +119 -0
  22. package/runtime/app/__init__.py +3 -0
  23. package/runtime/app/__main__.py +19 -0
  24. package/runtime/app/api/__init__.py +0 -0
  25. package/runtime/app/api/deps.py +69 -0
  26. package/runtime/app/api/v1.py +166 -0
  27. package/runtime/app/config.py +78 -0
  28. package/runtime/app/db/__init__.py +0 -0
  29. package/runtime/app/db/schemas.py +162 -0
  30. package/runtime/app/db/store.py +466 -0
  31. package/runtime/app/main.py +27 -0
  32. package/runtime/app/model_configuration.py +157 -0
  33. package/runtime/app/services/__init__.py +0 -0
  34. package/runtime/app/services/brain.py +2221 -0
  35. package/runtime/app/services/embedding.py +473 -0
  36. package/runtime/app/services/jobs.py +237 -0
  37. package/runtime/app/services/motion.py +66 -0
  38. package/runtime/app/services/pipeline.py +1911 -0
  39. package/runtime/app/services/scene.py +161 -0
  40. package/runtime/app/services/storage.py +44 -0
  41. package/runtime/app/services/transcription.py +667 -0
  42. package/runtime/app/services/vision.py +1441 -0
  43. package/runtime/app/utils/__init__.py +0 -0
  44. package/runtime/app/utils/timing.py +99 -0
  45. package/runtime/app/worker.py +20 -0
  46. package/runtime/pyproject.toml +56 -0
  47. package/runtime/requirements-cpu.txt +15 -0
  48. package/runtime/requirements-smoke.txt +7 -0
  49. package/runtime/requirements.txt +14 -0
  50. package/runtime/uv.lock +3637 -0
  51. package/scripts/grant-cloud-credits.sh +43 -0
  52. package/scripts/runtime.mjs +156 -0
  53. package/scripts/validate-indexing.mjs +168 -0
  54. package/tool.manifest.json +617 -0
File without changes
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ def normalized_important_ranges(
7
+ brief: dict[str, Any], duration_secs: float
8
+ ) -> list[tuple[float, float]]:
9
+ """Clamps a brief's requested ranges to the source duration, sorted and merged."""
10
+ ranges: list[tuple[float, float]] = []
11
+ for candidate in brief.get("importantRanges") or []:
12
+ try:
13
+ start = max(0.0, float(candidate.get("startSecs")))
14
+ end = min(duration_secs, float(candidate.get("endSecs")))
15
+ except (AttributeError, TypeError, ValueError):
16
+ continue
17
+ if end > start:
18
+ ranges.append((start, end))
19
+ ranges.sort()
20
+ merged: list[tuple[float, float]] = []
21
+ for start, end in ranges:
22
+ if merged and start <= merged[-1][1]:
23
+ merged[-1] = (merged[-1][0], max(merged[-1][1], end))
24
+ else:
25
+ merged.append((start, end))
26
+ return merged
27
+
28
+
29
+ def visual_sampling_interval(mode: str, duration_seconds: float) -> float:
30
+ """Seconds between sampled frames for a given indexing mode and source length."""
31
+ base_intervals = {"fast": 5.0, "balanced": 2.0, "thorough": 0.75}
32
+ max_samples = {"fast": 360, "balanced": 720, "thorough": 1_800}
33
+ return max(base_intervals[mode], duration_seconds / max_samples[mode])
34
+
35
+
36
+ def rebase_result_timestamps(result: dict[str, Any], offset_secs: float) -> None:
37
+ """Translates clip-relative evidence (a bounded/rebased inspection) to the source clock."""
38
+ offset_ms = round(offset_secs * 1_000)
39
+
40
+ def shift(item: dict[str, Any], *keys: str) -> None:
41
+ for key in keys:
42
+ if isinstance(item.get(key), (int, float)):
43
+ item[key] = round(float(item[key])) + offset_ms
44
+
45
+ for segment in result.get("transcript", []):
46
+ if not isinstance(segment, dict):
47
+ continue
48
+ shift(segment, "startMs", "endMs")
49
+ for word in segment.get("words", []):
50
+ if isinstance(word, dict):
51
+ shift(word, "startMs", "endMs")
52
+ for observation in result.get("visualObservations", []):
53
+ if isinstance(observation, dict):
54
+ shift(observation, "timeMs")
55
+ for track in result.get("tracks", []):
56
+ if isinstance(track, dict):
57
+ shift(track, "startMs", "endMs")
58
+ for overlay in result.get("recurringOverlayText", []):
59
+ if not isinstance(overlay, dict):
60
+ continue
61
+ shift(overlay, "firstSeenMs", "lastSeenMs")
62
+ if isinstance(overlay.get("timestampsMs"), list):
63
+ overlay["timestampsMs"] = [
64
+ round(float(timestamp)) + offset_ms
65
+ for timestamp in overlay["timestampsMs"]
66
+ if isinstance(timestamp, (int, float))
67
+ ]
68
+ for observation in result.get("semanticObservations", []):
69
+ if isinstance(observation, dict):
70
+ shift(observation, "startMs", "endMs")
71
+ for embedding in result.get("videoEmbeddings", []):
72
+ if isinstance(embedding, dict):
73
+ shift(embedding, "startMs", "endMs")
74
+ for entity in result.get("entities", []):
75
+ if not isinstance(entity, dict) or not isinstance(entity.get("timestampsMs"), list):
76
+ continue
77
+ entity["timestampsMs"] = [
78
+ round(float(timestamp)) + offset_ms
79
+ for timestamp in entity["timestampsMs"]
80
+ if isinstance(timestamp, (int, float))
81
+ ]
82
+
83
+ # Knowledge synthesis runs while a bounded source is still on its local
84
+ # clip clock. Its citations are persisted alongside raw evidence, so they
85
+ # must be translated by the same offset before a refinement is searchable.
86
+ summary = result.get("knowledgeSummary")
87
+ if not isinstance(summary, dict):
88
+ return
89
+ for key in ("stateHistory", "keyEvents"):
90
+ for item in summary.get(key, []):
91
+ if isinstance(item, dict):
92
+ shift(item, "startMs", "endMs")
93
+ for key in ("participants", "context"):
94
+ for item in summary.get(key, []):
95
+ if not isinstance(item, dict):
96
+ continue
97
+ for evidence in item.get("evidence", []):
98
+ if isinstance(evidence, dict):
99
+ shift(evidence, "startMs", "endMs")
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from app.config import Settings
6
+ from app.db.store import Store
7
+ from app.services.jobs import JobService
8
+
9
+
10
+ def main() -> None:
11
+ parser = argparse.ArgumentParser(description="Run one queued video intelligence job")
12
+ parser.add_argument("job_id")
13
+ args = parser.parse_args()
14
+ settings = Settings.from_env()
15
+ store = Store(settings.data_dir / "video-intelligence.sqlite3")
16
+ JobService(settings, store).run(args.job_id)
17
+
18
+
19
+ if __name__ == "__main__":
20
+ main()
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "larkup-video-intelligence-runtime"
7
+ version = "0.1.0"
8
+ description = "Portable FastAPI runtime for Larkup Video Intelligence"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12,<3.14"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Larkup" }]
13
+ dependencies = [
14
+ "boto3==1.40.1",
15
+ "cryptography>=46.0.7",
16
+ "fastapi>=0.115.6",
17
+ "numpy==1.26.4",
18
+ "opencv-python-headless==4.11.0.86",
19
+ "pydantic==2.11.7",
20
+ "python-multipart==0.0.32",
21
+ "requests==2.33.0",
22
+ "scenedetect==0.6.5",
23
+ "starlette>=0.49.1",
24
+ "uvicorn[standard]==0.35.0",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ cpu = [
29
+ "faster-whisper==1.1.1",
30
+ "onnxruntime==1.22.0",
31
+ "pyclipper==1.4.0",
32
+ "rapidocr-onnxruntime==1.4.4",
33
+ "Shapely==2.1.2",
34
+ "six==1.17.0",
35
+ ]
36
+ gpu = [
37
+ "faster-whisper==1.1.1",
38
+ "onnxruntime-gpu==1.22.0",
39
+ "paddleocr==3.0.3",
40
+ "rapidocr-onnxruntime==1.4.4",
41
+ "runpod>=1.7.14",
42
+ ]
43
+ test = ["httpx==0.28.1"]
44
+
45
+ [project.scripts]
46
+ larkup-video-runtime = "app.__main__:main"
47
+ larkup-video-runtime-worker = "app.worker:main"
48
+
49
+ [project.urls]
50
+ Repository = "https://github.com/Larkup-AI/larkup/tree/main/packages/marketplace-tools/video-intelligence"
51
+
52
+ [tool.setuptools]
53
+ packages = ["app", "app.api", "app.db", "app.services", "app.utils"]
54
+
55
+ [tool.uv]
56
+ package = true
@@ -0,0 +1,15 @@
1
+ fastapi==0.116.1
2
+ faster-whisper==1.1.1
3
+ numpy==1.26.4
4
+ onnxruntime==1.22.0
5
+ opencv-python-headless==4.11.0.86
6
+ Pillow==12.3.0
7
+ pyclipper==1.4.0
8
+ pydantic==2.11.7
9
+ python-multipart==0.0.32
10
+ PyYAML==6.0.3
11
+ requests==2.33.0
12
+ scenedetect==0.6.5
13
+ Shapely==2.1.2
14
+ six==1.17.0
15
+ uvicorn[standard]==0.35.0
@@ -0,0 +1,7 @@
1
+ fastapi==0.116.1
2
+ numpy==1.26.4
3
+ opencv-python-headless==4.11.0.86
4
+ pydantic==2.11.7
5
+ python-multipart==0.0.32
6
+ requests==2.33.0
7
+ uvicorn[standard]==0.35.0
@@ -0,0 +1,14 @@
1
+ boto3==1.40.1
2
+ fastapi==0.116.1
3
+ faster-whisper==1.1.1
4
+ numpy==1.26.4
5
+ onnxruntime-gpu==1.22.0
6
+ opencv-python-headless==4.11.0.86
7
+ paddleocr==3.0.3
8
+ pydantic==2.11.7
9
+ rapidocr-onnxruntime==1.4.4
10
+ python-multipart==0.0.32
11
+ requests==2.33.0
12
+ runpod==1.7.10
13
+ scenedetect==0.6.5
14
+ uvicorn[standard]==0.35.0