kinemotion 0.39.0__py3-none-any.whl → 0.39.1__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.
@@ -1,5 +1,10 @@
1
1
  """Shared debug overlay utilities for video rendering."""
2
2
 
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+
3
8
  import cv2
4
9
  import numpy as np
5
10
 
@@ -11,7 +16,7 @@ def create_video_writer(
11
16
  display_width: int,
12
17
  display_height: int,
13
18
  fps: float,
14
- ) -> tuple[cv2.VideoWriter, bool]:
19
+ ) -> tuple[cv2.VideoWriter, bool, str]:
15
20
  """
16
21
  Create a video writer with fallback codec support.
17
22
 
@@ -24,28 +29,43 @@ def create_video_writer(
24
29
  fps: Frames per second
25
30
 
26
31
  Returns:
27
- Tuple of (video_writer, needs_resize)
32
+ Tuple of (video_writer, needs_resize, used_codec)
28
33
  """
29
34
  needs_resize = (display_width != width) or (display_height != height)
30
35
 
31
- # Try H.264 codec first (better quality/compatibility), fallback to mp4v
32
- fourcc = cv2.VideoWriter_fourcc(*"avc1")
33
- writer = cv2.VideoWriter(output_path, fourcc, fps, (display_width, display_height))
36
+ # Try browser-compatible codecs first
37
+ # avc1/h264: H.264 (Most compatible)
38
+ # vp09: VP9 (Good compatibility)
39
+ # mp4v: MPEG-4 (Poor browser support, last resort)
40
+ codecs_to_try = ["avc1", "h264", "vp09", "mp4v"]
34
41
 
35
- # Check if writer opened successfully, fallback to mp4v if not
36
- if not writer.isOpened():
37
- fourcc = cv2.VideoWriter_fourcc(*"mp4v")
38
- writer = cv2.VideoWriter(
39
- output_path, fourcc, fps, (display_width, display_height)
40
- )
42
+ writer = None
43
+ used_codec = "mp4v" # Default fallback
41
44
 
42
- if not writer.isOpened():
45
+ for codec in codecs_to_try:
46
+ try:
47
+ fourcc = cv2.VideoWriter_fourcc(*codec)
48
+ writer = cv2.VideoWriter(
49
+ output_path, fourcc, fps, (display_width, display_height)
50
+ )
51
+ if writer.isOpened():
52
+ used_codec = codec
53
+ if codec == "mp4v":
54
+ print(
55
+ f"Warning: Fallback to {codec} codec. "
56
+ "Video may not play in browsers."
57
+ )
58
+ break
59
+ except Exception:
60
+ continue
61
+
62
+ if writer is None or not writer.isOpened():
43
63
  raise ValueError(
44
64
  f"Failed to create video writer for {output_path} with dimensions "
45
65
  f"{display_width}x{display_height}"
46
66
  )
47
67
 
48
- return writer, needs_resize
68
+ return writer, needs_resize, used_codec
49
69
 
50
70
 
51
71
  def write_overlay_frame(
@@ -95,11 +115,12 @@ class BaseDebugOverlayRenderer:
95
115
  display_height: Display height (considering SAR)
96
116
  fps: Frames per second
97
117
  """
118
+ self.output_path = output_path
98
119
  self.width = width
99
120
  self.height = height
100
121
  self.display_width = display_width
101
122
  self.display_height = display_height
102
- self.writer, self.needs_resize = create_video_writer(
123
+ self.writer, self.needs_resize, self.used_codec = create_video_writer(
103
124
  output_path, width, height, display_width, display_height, fps
104
125
  )
105
126
 
@@ -133,9 +154,64 @@ class BaseDebugOverlayRenderer:
133
154
  write_overlay_frame(self.writer, frame, self.display_width, self.display_height)
134
155
 
135
156
  def close(self) -> None:
136
- """Release video writer."""
157
+ """Release video writer and re-encode if possible."""
137
158
  self.writer.release()
138
159
 
160
+ # Post-process with ffmpeg ONLY if we fell back to the incompatible mp4v codec
161
+ if self.used_codec == "mp4v" and shutil.which("ffmpeg"):
162
+ temp_path = None
163
+ try:
164
+ temp_path = str(
165
+ Path(self.output_path).with_suffix(
166
+ ".temp" + Path(self.output_path).suffix
167
+ )
168
+ )
169
+
170
+ # Convert to H.264 with yuv420p pixel format for browser compatibility
171
+ # -y: Overwrite output file
172
+ # -vcodec libx264: Use H.264 codec
173
+ # -pix_fmt yuv420p: Required for wide browser support (Chrome,
174
+ # Safari, Firefox)
175
+ # -preset fast: Reasonable speed/compression tradeoff
176
+ # -crf 23: Standard quality
177
+ # -an: Remove audio (debug video has no audio)
178
+ cmd = [
179
+ "ffmpeg",
180
+ "-y",
181
+ "-i",
182
+ self.output_path,
183
+ "-vcodec",
184
+ "libx264",
185
+ "-pix_fmt",
186
+ "yuv420p",
187
+ "-preset",
188
+ "fast",
189
+ "-crf",
190
+ "23",
191
+ "-an",
192
+ temp_path,
193
+ ]
194
+
195
+ # Suppress output unless error
196
+ subprocess.run(
197
+ cmd,
198
+ check=True,
199
+ stdout=subprocess.DEVNULL,
200
+ stderr=subprocess.PIPE,
201
+ )
202
+
203
+ # Overwrite original file
204
+ os.replace(temp_path, self.output_path)
205
+
206
+ except subprocess.CalledProcessError as e:
207
+ print(f"Warning: Failed to re-encode debug video with ffmpeg: {e}")
208
+ if temp_path and os.path.exists(temp_path):
209
+ os.remove(temp_path)
210
+ except Exception as e:
211
+ print(f"Warning: Error during video post-processing: {e}")
212
+ if temp_path and os.path.exists(temp_path):
213
+ os.remove(temp_path)
214
+
139
215
  def __enter__(self) -> "BaseDebugOverlayRenderer":
140
216
  return self
141
217
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.39.0
3
+ Version: 0.39.1
4
4
  Summary: Video-based kinematic analysis for athletic performance
5
5
  Project-URL: Homepage, https://github.com/feniix/kinemotion
6
6
  Project-URL: Repository, https://github.com/feniix/kinemotion
@@ -12,7 +12,7 @@ kinemotion/cmj/validation_bounds.py,sha256=9ZTo68fl3ooyWjXXyTMRLpK9tFANa_rQf3oHh
12
12
  kinemotion/core/__init__.py,sha256=HsqolRa60cW3vrG8F9Lvr9WvWcs5hCmsTzSgo7imi-4,1278
13
13
  kinemotion/core/auto_tuning.py,sha256=wtCUMOhBChVJNXfEeku3GCMW4qED6MF-O_mv2sPTiVQ,11324
14
14
  kinemotion/core/cli_utils.py,sha256=zbnifPhD-OYofJioeYfJtshuWcl8OAEWtqCGVF4ctAI,7966
15
- kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
15
+ kinemotion/core/debug_overlay_utils.py,sha256=iouOW6Ss20dDH-IvDZuNnFtPIWzcav12xfUuDEulQtE,7312
16
16
  kinemotion/core/determinism.py,sha256=NwVrHqJiVxxFHTBPVy8aDBJH2SLIcYIpdGFp7glblB8,2515
17
17
  kinemotion/core/experimental.py,sha256=IK05AF4aZS15ke85hF3TWCqRIXU1AlD_XKzFz735Ua8,3640
18
18
  kinemotion/core/filtering.py,sha256=GsC9BB71V07LJJHgS2lsaxUAtJsupcUiwtZFDgODh8c,11417
@@ -31,8 +31,8 @@ kinemotion/dropjump/kinematics.py,sha256=kH-XM66wlOCYMpjvyb6_Qh5ZebyOfFZ47rmhgE1
31
31
  kinemotion/dropjump/metrics_validator.py,sha256=CrTlGup8q2kyPXtA6HNwm7_yq0AsBaDllG7RVZdXmYA,9342
32
32
  kinemotion/dropjump/validation_bounds.py,sha256=5b4I3CKPybuvrbn-nP5yCcGF_sH4Vtyw3a5AWWvWnBk,4645
33
33
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- kinemotion-0.39.0.dist-info/METADATA,sha256=ytvO76fvfntV_Vpjkc-4BezahvOO91a2I6X3PUM0eAk,26020
35
- kinemotion-0.39.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
36
- kinemotion-0.39.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
37
- kinemotion-0.39.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
38
- kinemotion-0.39.0.dist-info/RECORD,,
34
+ kinemotion-0.39.1.dist-info/METADATA,sha256=ZvdKUkH6H9g5TiLNRA1NLPhtaKe09Y7zDjfFZg6KDwU,26020
35
+ kinemotion-0.39.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
36
+ kinemotion-0.39.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
37
+ kinemotion-0.39.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
38
+ kinemotion-0.39.1.dist-info/RECORD,,