simple-video-utils 0.0.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.
@@ -0,0 +1 @@
1
+ """Simple video utilities for frame extraction and metadata."""
@@ -0,0 +1,95 @@
1
+ import io
2
+ from collections.abc import Generator
3
+ from typing import BinaryIO
4
+
5
+ import av
6
+ import numpy as np
7
+
8
+ from simple_video_utils.metadata import VideoMetadata, _open_container, video_metadata_from_bytes
9
+
10
+
11
+ def _generate_frames(
12
+ container: av.container.InputContainer,
13
+ start_frame: int = 0,
14
+ end_frame: int | None = None,
15
+ ) -> Generator[np.ndarray, None, None]:
16
+ """
17
+ Generate RGB frames from a container.
18
+
19
+ Args:
20
+ container: Open PyAV container.
21
+ start_frame: First frame index to yield (0-based).
22
+ end_frame: Last frame index to yield (inclusive), or None for all.
23
+
24
+ Yields:
25
+ RGB numpy arrays (H, W, 3).
26
+ """
27
+ frame_index = 0
28
+ for frame in container.decode(video=0):
29
+ if frame_index < start_frame:
30
+ frame_index += 1
31
+ continue
32
+
33
+ if end_frame is not None and frame_index > end_frame:
34
+ break
35
+
36
+ yield frame.to_ndarray(format='rgb24')
37
+ frame_index += 1
38
+
39
+ def read_frames_exact(
40
+ src: str,
41
+ start_frame: int,
42
+ end_frame: int | None = None,
43
+ ) -> Generator[np.ndarray, None, None]:
44
+ """
45
+ Return frames [start_frame, end_frame] inclusive as RGB np.ndarrays.
46
+ If end_frame is None, reads from start_frame to the end of the video.
47
+ Uses PyAV for efficient frame extraction.
48
+ """
49
+ if end_frame is not None:
50
+ assert end_frame >= start_frame >= 0, "invalid frame range"
51
+ else:
52
+ assert start_frame >= 0, "start_frame must be non-negative"
53
+
54
+ with _open_container(src) as container:
55
+ stream = container.streams.video[0]
56
+
57
+ # Seek to approximate start position if not starting from beginning
58
+ if start_frame > 0:
59
+ fps = float(stream.average_rate) if stream.average_rate else 30.0
60
+ seek_time_sec = max(0, (start_frame - 30) / fps)
61
+ # Convert seconds to stream time_base units
62
+ seek_timestamp = int(seek_time_sec / float(stream.time_base))
63
+ container.seek(seek_timestamp, stream=stream)
64
+
65
+ yield from _generate_frames(container, start_frame, end_frame)
66
+
67
+
68
+ def read_frames_from_stream(
69
+ stream: BinaryIO,
70
+ skip_frames: int = 0,
71
+ ) -> tuple[VideoMetadata, Generator[np.ndarray, None, None]]:
72
+ """
73
+ Read frames from a video stream (file-like object).
74
+
75
+ Args:
76
+ stream: A file-like object containing video data (e.g., uploaded file).
77
+ skip_frames: Number of initial frames to skip (for resume support).
78
+
79
+ Returns:
80
+ A tuple of (VideoMetadata, frame_generator).
81
+ The generator yields np.ndarray frames in RGB format (H, W, 3).
82
+
83
+ Note:
84
+ PyAV handles format detection and seeking automatically.
85
+ Works with MP4, WebM, and other formats.
86
+ """
87
+ video_data = stream.read()
88
+ meta = video_metadata_from_bytes(video_data)
89
+
90
+ def frame_generator() -> Generator[np.ndarray, None, None]:
91
+ """Generator that yields frames from the video data."""
92
+ with _open_container(io.BytesIO(video_data)) as container:
93
+ yield from _generate_frames(container, start_frame=skip_frames)
94
+
95
+ return meta, frame_generator()
@@ -0,0 +1,60 @@
1
+ import io
2
+ from contextlib import contextmanager
3
+ from functools import lru_cache
4
+ from typing import NamedTuple
5
+
6
+ import av
7
+
8
+
9
+ class VideoMetadata(NamedTuple):
10
+ width: int
11
+ height: int
12
+ fps: float
13
+ nb_frames: int | None
14
+ time_base: str | None
15
+
16
+
17
+ @contextmanager
18
+ def _open_container(source: str | io.BytesIO):
19
+ """Context manager for safely opening and closing PyAV containers."""
20
+ container = None
21
+ try:
22
+ container = av.open(source)
23
+ yield container
24
+ except Exception as e:
25
+ msg = "Failed to open video"
26
+ raise RuntimeError(msg) from e
27
+ finally:
28
+ if container:
29
+ container.close()
30
+
31
+
32
+ def _get_metadata_from_container(container: av.container.InputContainer) -> VideoMetadata:
33
+ """Extract metadata from an open PyAV container."""
34
+ stream = container.streams.video[0]
35
+ fps = float(stream.average_rate) if stream.average_rate else 0.0
36
+ nb_frames = stream.frames if stream.frames > 0 else None
37
+ time_base = str(stream.time_base) if stream.time_base else None
38
+
39
+ return VideoMetadata(
40
+ width=stream.width,
41
+ height=stream.height,
42
+ fps=fps,
43
+ nb_frames=nb_frames,
44
+ time_base=time_base,
45
+ )
46
+
47
+
48
+
49
+
50
+ def video_metadata_from_bytes(data: bytes) -> VideoMetadata:
51
+ """Return key video stream metadata from video bytes."""
52
+ with _open_container(io.BytesIO(data)) as container:
53
+ return _get_metadata_from_container(container)
54
+
55
+
56
+ @lru_cache(maxsize=8)
57
+ def video_metadata(url_or_path: str) -> VideoMetadata:
58
+ """Return key video stream metadata."""
59
+ with _open_container(url_or_path) as container:
60
+ return _get_metadata_from_container(container)
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple-video-utils
3
+ Version: 0.0.1
4
+ Summary: Shared utilities for processing videos for sign language.
5
+ Author-email: Amit Moryossef <amit@sign.mt>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: av
11
+ Requires-Dist: numpy
12
+ Provides-Extra: dev
13
+ Requires-Dist: ruff; extra == "dev"
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pytest-xdist; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # Simple Video Utils
19
+
20
+ Lightweight utilities for extracting frames and metadata from videos. Built for sign language processing workflows.
21
+
22
+ ![Python](https://img.shields.io/badge/python-3.10+-blue)
23
+ [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
24
+
25
+ ## Goal
26
+
27
+ Provide simple, efficient tools for video processing in sign language research and applications.
28
+ Uses PyAV for fast frame extraction with support for multiple formats (MP4, WebM) and remote URLs.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install simple-video-utils
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Extract Video Metadata
39
+
40
+ ```python
41
+ from simple_video_utils.metadata import video_metadata
42
+
43
+ meta = video_metadata("video.mp4")
44
+ print(f"{meta.width}x{meta.height} @ {meta.fps} fps")
45
+ # Output: VideoMetadata(width=1920, height=1080, fps=30.0, nb_frames=450, time_base='1/15360')
46
+ ```
47
+
48
+ ### Read Frames from File
49
+
50
+ ```python
51
+ from simple_video_utils.frames import read_frames_exact
52
+
53
+ # Read specific frame range (inclusive)
54
+ frames = list(read_frames_exact("video.mp4", start_frame=0, end_frame=10))
55
+ # Returns 11 frames as numpy arrays (H, W, 3) in RGB format
56
+
57
+ # Read from frame to end of video
58
+ frames = list(read_frames_exact("video.mp4", start_frame=5, end_frame=None))
59
+ ```
60
+
61
+ ### Read Frames from Stream
62
+
63
+ ```python
64
+ from simple_video_utils.frames import read_frames_from_stream
65
+
66
+ # Useful for uploaded files or in-memory video data
67
+ with open("video.mp4", "rb") as f:
68
+ meta, frames_gen = read_frames_from_stream(f)
69
+ for frame in frames_gen:
70
+ # Process each frame (numpy array)
71
+ pass
72
+ ```
73
+
74
+ ### Remote Videos
75
+
76
+ ```python
77
+ from simple_video_utils.metadata import video_metadata
78
+ from simple_video_utils.frames import read_frames_exact
79
+
80
+ # Works with remote URLs
81
+ url = "https://example.com/video.mp4"
82
+ meta = video_metadata(url)
83
+ frames = list(read_frames_exact(url, 0, 5))
84
+ ```
85
+
86
+ ## Development
87
+
88
+ ```bash
89
+ pip install -e ".[dev]"
90
+ pytest tests/
91
+ ruff check .
92
+ ```
93
+
@@ -0,0 +1,8 @@
1
+ simple_video_utils/__init__.py,sha256=LRnmuFyziKNpM_Mf2x4UarAiZsOKGunmMDf98WTm1SY,64
2
+ simple_video_utils/frames.py,sha256=VkX-OHGrOc_oJDI7j8xIarVu2jHWdSYEouFgk4PfsJE,3115
3
+ simple_video_utils/metadata.py,sha256=1JALf1AligxFwfArgGjgoFFtrrQTMKsnzY2yH3oeQVk,1672
4
+ simple_video_utils-0.0.1.dist-info/licenses/LICENSE,sha256=-85p81jfHERhmQnds40xOFt4q3cTlX0GFbSUuJU1jxI,1061
5
+ simple_video_utils-0.0.1.dist-info/METADATA,sha256=IIfZpWWdej-F_xb9jKf7TTQadTWlLv6Xahlg_HeOYI8,2378
6
+ simple_video_utils-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ simple_video_utils-0.0.1.dist-info/top_level.txt,sha256=9aYY6Qrg3bqG-QZsunQV8-_qXabGWK26Ub7JC8Eyyys,19
8
+ simple_video_utils-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 sign
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ simple_video_utils