media-engine 0.1.0__py3-none-any.whl

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 (70) hide show
  1. cli/clip.py +79 -0
  2. cli/faces.py +91 -0
  3. cli/metadata.py +68 -0
  4. cli/motion.py +77 -0
  5. cli/objects.py +94 -0
  6. cli/ocr.py +93 -0
  7. cli/scenes.py +57 -0
  8. cli/telemetry.py +65 -0
  9. cli/transcript.py +76 -0
  10. media_engine/__init__.py +7 -0
  11. media_engine/_version.py +34 -0
  12. media_engine/app.py +80 -0
  13. media_engine/batch/__init__.py +56 -0
  14. media_engine/batch/models.py +99 -0
  15. media_engine/batch/processor.py +1131 -0
  16. media_engine/batch/queue.py +232 -0
  17. media_engine/batch/state.py +30 -0
  18. media_engine/batch/timing.py +321 -0
  19. media_engine/cli.py +17 -0
  20. media_engine/config.py +674 -0
  21. media_engine/extractors/__init__.py +75 -0
  22. media_engine/extractors/clip.py +401 -0
  23. media_engine/extractors/faces.py +459 -0
  24. media_engine/extractors/frame_buffer.py +351 -0
  25. media_engine/extractors/frames.py +402 -0
  26. media_engine/extractors/metadata/__init__.py +127 -0
  27. media_engine/extractors/metadata/apple.py +169 -0
  28. media_engine/extractors/metadata/arri.py +118 -0
  29. media_engine/extractors/metadata/avchd.py +208 -0
  30. media_engine/extractors/metadata/avchd_gps.py +270 -0
  31. media_engine/extractors/metadata/base.py +688 -0
  32. media_engine/extractors/metadata/blackmagic.py +139 -0
  33. media_engine/extractors/metadata/camera_360.py +276 -0
  34. media_engine/extractors/metadata/canon.py +290 -0
  35. media_engine/extractors/metadata/dji.py +371 -0
  36. media_engine/extractors/metadata/dv.py +121 -0
  37. media_engine/extractors/metadata/ffmpeg.py +76 -0
  38. media_engine/extractors/metadata/generic.py +119 -0
  39. media_engine/extractors/metadata/gopro.py +256 -0
  40. media_engine/extractors/metadata/red.py +305 -0
  41. media_engine/extractors/metadata/registry.py +114 -0
  42. media_engine/extractors/metadata/sony.py +442 -0
  43. media_engine/extractors/metadata/tesla.py +157 -0
  44. media_engine/extractors/motion.py +765 -0
  45. media_engine/extractors/objects.py +245 -0
  46. media_engine/extractors/objects_qwen.py +754 -0
  47. media_engine/extractors/ocr.py +268 -0
  48. media_engine/extractors/scenes.py +82 -0
  49. media_engine/extractors/shot_type.py +217 -0
  50. media_engine/extractors/telemetry.py +262 -0
  51. media_engine/extractors/transcribe.py +579 -0
  52. media_engine/extractors/translate.py +121 -0
  53. media_engine/extractors/vad.py +263 -0
  54. media_engine/main.py +68 -0
  55. media_engine/py.typed +0 -0
  56. media_engine/routers/__init__.py +15 -0
  57. media_engine/routers/batch.py +78 -0
  58. media_engine/routers/health.py +93 -0
  59. media_engine/routers/models.py +211 -0
  60. media_engine/routers/settings.py +87 -0
  61. media_engine/routers/utils.py +135 -0
  62. media_engine/schemas.py +581 -0
  63. media_engine/utils/__init__.py +5 -0
  64. media_engine/utils/logging.py +54 -0
  65. media_engine/utils/memory.py +49 -0
  66. media_engine-0.1.0.dist-info/METADATA +276 -0
  67. media_engine-0.1.0.dist-info/RECORD +70 -0
  68. media_engine-0.1.0.dist-info/WHEEL +4 -0
  69. media_engine-0.1.0.dist-info/entry_points.txt +11 -0
  70. media_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
cli/clip.py ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python3
2
+ """Extract CLIP embeddings from video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import (
11
+ analyze_motion,
12
+ decode_frames,
13
+ extract_clip,
14
+ get_adaptive_timestamps,
15
+ )
16
+
17
+
18
+ def main():
19
+ parser = argparse.ArgumentParser(description="Extract CLIP embeddings from video")
20
+ parser.add_argument("file", help="Path to video file")
21
+ parser.add_argument(
22
+ "--model",
23
+ type=str,
24
+ default=None,
25
+ help="CLIP model name (e.g., ViT-B-32, ViT-L-14)",
26
+ )
27
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
28
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
29
+
30
+ args = parser.parse_args()
31
+
32
+ if args.verbose:
33
+ logging.basicConfig(level=logging.DEBUG)
34
+ else:
35
+ logging.basicConfig(level=logging.WARNING)
36
+
37
+ try:
38
+ start_time = time.perf_counter()
39
+
40
+ # Run motion analysis to get adaptive timestamps
41
+ motion = analyze_motion(args.file)
42
+ timestamps = get_adaptive_timestamps(motion)
43
+
44
+ # Decode frames once using shared buffer
45
+ frame_buffer = decode_frames(args.file, timestamps=timestamps)
46
+
47
+ # Extract CLIP embeddings using shared frame buffer
48
+ result = extract_clip(
49
+ args.file,
50
+ frame_buffer=frame_buffer,
51
+ model_name=args.model,
52
+ )
53
+ elapsed = time.perf_counter() - start_time
54
+
55
+ if args.json:
56
+ output = result.model_dump()
57
+ output["elapsed_seconds"] = round(elapsed, 2)
58
+ print(json.dumps(output, indent=2, default=str))
59
+ else:
60
+ print(f"File: {args.file}")
61
+ print(f"Model: {result.model}")
62
+ print(f"Segments: {len(result.segments)}")
63
+ if result.segments:
64
+ print(f"Embedding dimensions: {len(result.segments[0].embedding)}")
65
+ print()
66
+ for i, seg in enumerate(result.segments[:10], 1): # Show first 10
67
+ print(f" {i}: {seg.start:.2f}s-{seg.end:.2f}s embedding[{len(seg.embedding)}]")
68
+ if len(result.segments) > 10:
69
+ print(f" ... and {len(result.segments) - 10} more")
70
+ print()
71
+ print(f"Elapsed: {elapsed:.2f}s")
72
+
73
+ except Exception as e:
74
+ print(f"Error: {e}", file=sys.stderr)
75
+ sys.exit(1)
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()
cli/faces.py ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ """Detect faces in video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import (
11
+ analyze_motion,
12
+ decode_frames,
13
+ extract_faces,
14
+ get_adaptive_timestamps,
15
+ )
16
+
17
+
18
+ def main():
19
+ parser = argparse.ArgumentParser(description="Detect faces in video file")
20
+ parser.add_argument("file", help="Path to video file")
21
+ parser.add_argument(
22
+ "--sample-fps",
23
+ type=float,
24
+ default=1.0,
25
+ help="Sample rate for face detection (default: 1.0)",
26
+ )
27
+ parser.add_argument(
28
+ "--min-face-size",
29
+ type=int,
30
+ default=80,
31
+ help="Minimum face size in pixels (default: 80)",
32
+ )
33
+ parser.add_argument(
34
+ "--min-confidence",
35
+ type=float,
36
+ default=0.5,
37
+ help="Minimum detection confidence (default: 0.5)",
38
+ )
39
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
40
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
41
+
42
+ args = parser.parse_args()
43
+
44
+ if args.verbose:
45
+ logging.basicConfig(level=logging.DEBUG)
46
+ else:
47
+ logging.basicConfig(level=logging.WARNING)
48
+
49
+ try:
50
+ start_time = time.perf_counter()
51
+
52
+ # Run motion analysis to get adaptive timestamps
53
+ motion = analyze_motion(args.file)
54
+ timestamps = get_adaptive_timestamps(motion)
55
+
56
+ # Decode frames once using shared buffer
57
+ frame_buffer = decode_frames(args.file, timestamps=timestamps)
58
+
59
+ # Extract faces using shared frame buffer
60
+ result = extract_faces(
61
+ args.file,
62
+ frame_buffer=frame_buffer,
63
+ min_face_size=args.min_face_size,
64
+ min_confidence=args.min_confidence,
65
+ )
66
+ elapsed = time.perf_counter() - start_time
67
+
68
+ if args.json:
69
+ output = result.model_dump()
70
+ output["elapsed_seconds"] = round(elapsed, 2)
71
+ print(json.dumps(output, indent=2, default=str))
72
+ else:
73
+ print(f"File: {args.file}")
74
+ print(f"Faces detected: {result.count}")
75
+ print(f"Unique estimate: {result.unique_estimate}")
76
+ print()
77
+ for i, face in enumerate(result.detections[:20], 1): # Show first 20
78
+ bbox = face.bbox
79
+ print(f" {i}: t={face.timestamp:.2f}s " f"box=({bbox.x},{bbox.y},{bbox.width}x{bbox.height}) " f"conf={face.confidence:.2f}")
80
+ if result.count > 20:
81
+ print(f" ... and {result.count - 20} more")
82
+ print()
83
+ print(f"Elapsed: {elapsed:.2f}s")
84
+
85
+ except Exception as e:
86
+ print(f"Error: {e}", file=sys.stderr)
87
+ sys.exit(1)
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
cli/metadata.py ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ """Extract metadata from video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import extract_metadata
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="Extract metadata from video file")
15
+ parser.add_argument("file", help="Path to video file")
16
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
17
+ parser.add_argument("--json", action="store_true", help="Output as JSON (default: human-readable)")
18
+
19
+ args = parser.parse_args()
20
+
21
+ if args.verbose:
22
+ logging.basicConfig(level=logging.DEBUG)
23
+ else:
24
+ logging.basicConfig(level=logging.WARNING)
25
+
26
+ try:
27
+ start_time = time.perf_counter()
28
+ result = extract_metadata(args.file)
29
+ elapsed = time.perf_counter() - start_time
30
+
31
+ if args.json:
32
+ output = result.model_dump()
33
+ output["elapsed_seconds"] = round(elapsed, 2)
34
+ print(json.dumps(output, indent=2, default=str))
35
+ else:
36
+ print(f"File: {args.file}")
37
+ print(f"Duration: {result.duration}s")
38
+ print(f"Resolution: {result.resolution.width}x{result.resolution.height}")
39
+ print(f"FPS: {result.fps}")
40
+ if result.video_codec:
41
+ print(f"Codec: {result.video_codec.name}")
42
+ if result.device:
43
+ print(f"Device: {result.device.make} {result.device.model}")
44
+ if result.gps:
45
+ print(f"GPS: {result.gps.latitude}, {result.gps.longitude}")
46
+ if result.gps_track:
47
+ track = result.gps_track
48
+ bounds = track.bounds
49
+ if bounds:
50
+ lat_range = bounds["max_lat"] - bounds["min_lat"]
51
+ lon_range = bounds["max_lon"] - bounds["min_lon"]
52
+ print(f"GPS Track: {track.count} points (lat range: {lat_range:.6f}, lon range: {lon_range:.6f})")
53
+ if result.shot_type:
54
+ print(f"Shot type: {result.shot_type.primary} ({result.shot_type.confidence:.2f})")
55
+ if result.keyframes:
56
+ kf = result.keyframes
57
+ interval_type = "fixed GOP" if kf.is_fixed_interval else "irregular (likely cuts)"
58
+ print(f"Keyframes: {kf.count} ({interval_type}, avg {kf.avg_interval}s)")
59
+ print()
60
+ print(f"Elapsed: {elapsed:.2f}s")
61
+
62
+ except Exception as e:
63
+ print(f"Error: {e}", file=sys.stderr)
64
+ sys.exit(1)
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()
cli/motion.py ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python3
2
+ """Analyze camera motion in video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import analyze_motion
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="Analyze camera motion in video")
15
+ parser.add_argument("file", help="Path to video file")
16
+ parser.add_argument(
17
+ "--sample-fps",
18
+ type=float,
19
+ default=2.0,
20
+ help="Sample rate for motion analysis (default: 2.0)",
21
+ )
22
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
23
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
24
+
25
+ args = parser.parse_args()
26
+
27
+ if args.verbose:
28
+ logging.basicConfig(level=logging.DEBUG)
29
+ else:
30
+ logging.basicConfig(level=logging.WARNING)
31
+
32
+ try:
33
+ start_time = time.perf_counter()
34
+ result = analyze_motion(args.file, sample_fps=args.sample_fps)
35
+ elapsed = time.perf_counter() - start_time
36
+
37
+ if args.json:
38
+ data = {
39
+ "duration": result.duration,
40
+ "fps": result.fps,
41
+ "primary_motion": result.primary_motion.value,
42
+ "avg_intensity": float(result.avg_intensity),
43
+ "is_stable": result.is_stable,
44
+ "segments": [
45
+ {
46
+ "start": s.start,
47
+ "end": s.end,
48
+ "motion_type": s.motion_type.value,
49
+ "intensity": float(s.intensity),
50
+ }
51
+ for s in result.segments
52
+ ],
53
+ "elapsed_seconds": round(elapsed, 2),
54
+ }
55
+ print(json.dumps(data, indent=2))
56
+ else:
57
+ print(f"File: {args.file}")
58
+ print(f"Duration: {result.duration:.2f}s")
59
+ print(f"Primary motion: {result.primary_motion.value}")
60
+ print(f"Avg intensity: {result.avg_intensity:.2f}")
61
+ print(f"Stable: {result.is_stable}")
62
+ print(f"Segments: {len(result.segments)}")
63
+ print()
64
+ for i, seg in enumerate(result.segments[:10], 1): # Show first 10
65
+ print(f" {i}: {seg.start:.2f}s-{seg.end:.2f}s " f"{seg.motion_type.value} (intensity: {seg.intensity:.2f})")
66
+ if len(result.segments) > 10:
67
+ print(f" ... and {len(result.segments) - 10} more")
68
+ print()
69
+ print(f"Elapsed: {elapsed:.2f}s")
70
+
71
+ except Exception as e:
72
+ print(f"Error: {e}", file=sys.stderr)
73
+ sys.exit(1)
74
+
75
+
76
+ if __name__ == "__main__":
77
+ main()
cli/objects.py ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python3
2
+ """Detect objects in video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import (
11
+ analyze_motion,
12
+ decode_frames,
13
+ extract_objects,
14
+ extract_objects_qwen,
15
+ get_adaptive_timestamps,
16
+ )
17
+
18
+
19
+ def main():
20
+ parser = argparse.ArgumentParser(description="Detect objects in video file")
21
+ parser.add_argument("file", help="Path to video file")
22
+ parser.add_argument(
23
+ "--detector",
24
+ type=str,
25
+ default="yolo",
26
+ choices=["yolo", "qwen"],
27
+ help="Object detector to use (default: yolo)",
28
+ )
29
+ parser.add_argument(
30
+ "--sample-fps",
31
+ type=float,
32
+ default=2.0,
33
+ help="Sample rate for YOLO detection (default: 2.0)",
34
+ )
35
+ parser.add_argument(
36
+ "--min-confidence",
37
+ type=float,
38
+ default=0.5,
39
+ help="Minimum detection confidence (default: 0.5)",
40
+ )
41
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
42
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
43
+
44
+ args = parser.parse_args()
45
+
46
+ if args.verbose:
47
+ logging.basicConfig(level=logging.DEBUG)
48
+ else:
49
+ logging.basicConfig(level=logging.WARNING)
50
+
51
+ try:
52
+ start_time = time.perf_counter()
53
+ if args.detector == "qwen":
54
+ result = extract_objects_qwen(args.file)
55
+ else:
56
+ # Run motion analysis to get adaptive timestamps
57
+ motion = analyze_motion(args.file)
58
+ timestamps = get_adaptive_timestamps(motion)
59
+
60
+ # Decode frames once using shared buffer
61
+ frame_buffer = decode_frames(args.file, timestamps=timestamps)
62
+
63
+ # Extract objects using shared frame buffer
64
+ result = extract_objects(
65
+ args.file,
66
+ frame_buffer=frame_buffer,
67
+ min_confidence=args.min_confidence,
68
+ )
69
+ elapsed = time.perf_counter() - start_time
70
+
71
+ if args.json:
72
+ output = result.model_dump()
73
+ output["elapsed_seconds"] = round(elapsed, 2)
74
+ print(json.dumps(output, indent=2, default=str))
75
+ else:
76
+ print(f"File: {args.file}")
77
+ print(f"Detector: {args.detector}")
78
+ print(f"Detections: {len(result.detections)}")
79
+ print()
80
+ print("Summary:")
81
+ for label, count in sorted(result.summary.items(), key=lambda x: x[1], reverse=True)[:15]:
82
+ print(f" {label}: {count}")
83
+ if len(result.summary) > 15:
84
+ print(f" ... and {len(result.summary) - 15} more types")
85
+ print()
86
+ print(f"Elapsed: {elapsed:.2f}s")
87
+
88
+ except Exception as e:
89
+ print(f"Error: {e}", file=sys.stderr)
90
+ sys.exit(1)
91
+
92
+
93
+ if __name__ == "__main__":
94
+ main()
cli/ocr.py ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ """Extract text (OCR) from video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import (
11
+ analyze_motion,
12
+ decode_frames,
13
+ extract_ocr,
14
+ get_adaptive_timestamps,
15
+ )
16
+
17
+
18
+ def main():
19
+ parser = argparse.ArgumentParser(description="Extract text (OCR) from video file")
20
+ parser.add_argument("file", help="Path to video file")
21
+ parser.add_argument(
22
+ "--min-confidence",
23
+ type=float,
24
+ default=0.5,
25
+ help="Minimum detection confidence (default: 0.5)",
26
+ )
27
+ parser.add_argument(
28
+ "--skip-prefilter",
29
+ action="store_true",
30
+ help="Skip MSER pre-filter (run OCR on all frames)",
31
+ )
32
+ parser.add_argument(
33
+ "--languages",
34
+ type=str,
35
+ default=None,
36
+ help="OCR languages, comma-separated (e.g., 'en,no,de')",
37
+ )
38
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
39
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
40
+
41
+ args = parser.parse_args()
42
+
43
+ if args.verbose:
44
+ logging.basicConfig(level=logging.DEBUG)
45
+ else:
46
+ logging.basicConfig(level=logging.WARNING)
47
+
48
+ languages = None
49
+ if args.languages:
50
+ languages = [lang.strip() for lang in args.languages.split(",")]
51
+
52
+ try:
53
+ start_time = time.perf_counter()
54
+
55
+ # Run motion analysis to get adaptive timestamps
56
+ motion = analyze_motion(args.file)
57
+ timestamps = get_adaptive_timestamps(motion)
58
+
59
+ # Decode frames once using shared buffer
60
+ frame_buffer = decode_frames(args.file, timestamps=timestamps)
61
+
62
+ # Extract OCR using shared frame buffer
63
+ result = extract_ocr(
64
+ args.file,
65
+ frame_buffer=frame_buffer,
66
+ min_confidence=args.min_confidence,
67
+ skip_prefilter=args.skip_prefilter,
68
+ languages=languages,
69
+ )
70
+ elapsed = time.perf_counter() - start_time
71
+
72
+ if args.json:
73
+ output = result.model_dump()
74
+ output["elapsed_seconds"] = round(elapsed, 2)
75
+ print(json.dumps(output, indent=2, default=str))
76
+ else:
77
+ print(f"File: {args.file}")
78
+ print(f"Text regions detected: {len(result.detections)}")
79
+ print()
80
+ for i, det in enumerate(result.detections[:20], 1): # Show first 20
81
+ print(f' {i}: t={det.timestamp:.2f}s "{det.text}" (conf={det.confidence:.2f})')
82
+ if len(result.detections) > 20:
83
+ print(f" ... and {len(result.detections) - 20} more")
84
+ print()
85
+ print(f"Elapsed: {elapsed:.2f}s")
86
+
87
+ except Exception as e:
88
+ print(f"Error: {e}", file=sys.stderr)
89
+ sys.exit(1)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
cli/scenes.py ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python3
2
+ """Detect scene boundaries in video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import extract_scenes
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="Detect scene boundaries in video")
15
+ parser.add_argument("file", help="Path to video file")
16
+ parser.add_argument(
17
+ "--threshold",
18
+ type=float,
19
+ default=27.0,
20
+ help="Content detection threshold (lower=more sensitive, default: 27.0)",
21
+ )
22
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
23
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
24
+
25
+ args = parser.parse_args()
26
+
27
+ if args.verbose:
28
+ logging.basicConfig(level=logging.DEBUG)
29
+ else:
30
+ logging.basicConfig(level=logging.WARNING)
31
+
32
+ try:
33
+ start_time = time.perf_counter()
34
+ result = extract_scenes(args.file, threshold=args.threshold)
35
+ elapsed = time.perf_counter() - start_time
36
+
37
+ if args.json:
38
+ output = result.model_dump()
39
+ output["elapsed_seconds"] = round(elapsed, 2)
40
+ print(json.dumps(output, indent=2, default=str))
41
+ else:
42
+ print(f"File: {args.file}")
43
+ print(f"Scenes detected: {result.count}")
44
+ print()
45
+ for i, scene in enumerate(result.detections, 1):
46
+ duration = scene.end - scene.start
47
+ print(f" Scene {i}: {scene.start:.2f}s - {scene.end:.2f}s ({duration:.2f}s)")
48
+ print()
49
+ print(f"Elapsed: {elapsed:.2f}s")
50
+
51
+ except Exception as e:
52
+ print(f"Error: {e}", file=sys.stderr)
53
+ sys.exit(1)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
cli/telemetry.py ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+ """Extract telemetry (GPS/flight data) from video file."""
3
+
4
+ import argparse
5
+ import json
6
+ import logging
7
+ import sys
8
+ import time
9
+
10
+ from media_engine.extractors import extract_telemetry
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description="Extract telemetry from video file")
15
+ parser.add_argument("file", help="Path to video file")
16
+ parser.add_argument(
17
+ "--gpx",
18
+ action="store_true",
19
+ help="Output as GPX format instead of JSON",
20
+ )
21
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
22
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
23
+
24
+ args = parser.parse_args()
25
+
26
+ if args.verbose:
27
+ logging.basicConfig(level=logging.DEBUG)
28
+ else:
29
+ logging.basicConfig(level=logging.WARNING)
30
+
31
+ try:
32
+ start_time = time.perf_counter()
33
+ result = extract_telemetry(args.file)
34
+ elapsed = time.perf_counter() - start_time
35
+
36
+ if result is None:
37
+ print("No telemetry data found", file=sys.stderr)
38
+ sys.exit(0)
39
+
40
+ if args.gpx:
41
+ print(result.to_gpx())
42
+ elif args.json:
43
+ output = result.model_dump()
44
+ output["elapsed_seconds"] = round(elapsed, 2)
45
+ print(json.dumps(output, indent=2, default=str))
46
+ else:
47
+ print(f"File: {args.file}")
48
+ print(f"Source: {result.source}")
49
+ print(f"Points: {len(result.points)}")
50
+ print()
51
+ for i, pt in enumerate(result.points[:10], 1): # Show first 10
52
+ alt = f" alt={pt.altitude:.1f}m" if pt.altitude else ""
53
+ print(f" {i}: ({pt.latitude:.6f}, {pt.longitude:.6f}){alt}")
54
+ if len(result.points) > 10:
55
+ print(f" ... and {len(result.points) - 10} more")
56
+ print()
57
+ print(f"Elapsed: {elapsed:.2f}s")
58
+
59
+ except Exception as e:
60
+ print(f"Error: {e}", file=sys.stderr)
61
+ sys.exit(1)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()