diff --git a/Dockerfile.python-3.12 b/Dockerfile.python-3.12 new file mode 100644 index 0000000..f537f4a --- /dev/null +++ b/Dockerfile.python-3.12 @@ -0,0 +1,44 @@ +FROM ubuntu:plucky + +# Install build dependencies and tools +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + build-essential \ + checkinstall \ + zlib1g-dev \ + libncurses5-dev \ + libgdbm-dev \ + libnss3-dev \ + libssl-dev \ + libreadline-dev \ + libffi-dev \ + libsqlite3-dev \ + libbz2-dev \ + tk-dev \ + liblzma-dev \ + wget \ + tar \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} + +# Download Python 3.12.11 source +RUN wget https://www.python.org/ftp/python/3.12.11/Python-3.12.11.tgz -O /tmp/Python-3.12.11.tgz \ + && tar -xvf /tmp/Python-3.12.11.tgz -C /tmp \ + && rm /tmp/Python-3.12.11.tgz + +# Change to source directory +WORKDIR /tmp/Python-3.12.11 + +# Configure the build +RUN ./configure --enable-optimizations --enable-shared --prefix=/usr + +# Compile +RUN make -j $(nproc) + +RUN make altinstall DESTDIR=/tmp/python-build \ + && mkdir -p /tmp/python-build/DEBIAN \ + && echo "Package: python3.12-custom\nVersion: 3.12.11-1\nSection: custom\nPriority: optional\nArchitecture: amd64\nMaintainer: you@example.com\nDescription: Custom Python 3.12.11 build" > /tmp/python-build/DEBIAN/control \ + && dpkg-deb --build /tmp/python-build /python3.12-custom_3.12.11-1_amd64.deb \ + && rm -rf /tmp/python-build + +CMD ["/bin/bash", "-c", "cp /python3.12-custom_3.12.11-1_*.deb /build/"] \ No newline at end of file diff --git a/README.md b/README.md index 4c02e9d..2053f51 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,23 @@ The backend acts as the signaling server, routing WebRTC negotiation messages be ## Development Tools +### Python version + +Many of the packages required for ML are not available or with correct versions +with Python 3.13 (default for Ubuntu:Plucky.) If your host is running +python>3.12 and there aren't any python-3.12 packages available to install, you +can use the 'python-3.12' container to build a python3.12 package: + +``` +# Build the .deb +docker compose build python-3.12 +# Copy the .deb from the container image to ./build +docker compose run python-3.12 +# Install the pkg: +sudo dpkg -i build/python*.deb +``` + + ### TypeScript Type Generation The project includes automatic TypeScript type generation from the FastAPI OpenAPI schema: diff --git a/docker-compose.yml b/docker-compose.yml index 8afcd91..43c71e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,17 @@ services: networks: - ai-voicebot-net + python-3.12: + container_name: python-3.12 + hostname: python-3.12 + build: + context: . + dockerfile: Dockerfile.python-3.12 + restart: no + volumes: + - ./build:/build:rw + + server: container_name: server hostname: server diff --git a/voicebot/main.py b/voicebot/main.py index 04a4b2d..68c0f4d 100644 --- a/voicebot/main.py +++ b/voicebot/main.py @@ -3,7 +3,6 @@ WebRTC Media Agent for Python This module provides WebRTC signaling server communication and peer connection management. Synthetic audio/video track creation is handled by the synthetic_media module. -Test change to trigger reload - TESTING RELOAD NOW """ from __future__ import annotations diff --git a/voicebot/pyproject.toml b/voicebot/pyproject.toml index aa2c5f5..fe47fe6 100644 --- a/voicebot/pyproject.toml +++ b/voicebot/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "aiortc>=1.13.0", - "numpy>=2.3.2", + "numpy<2.3", "opencv-python>=4.11.0.86", "pydantic>=2.11.7", "websockets>=15.0.1", diff --git a/voicebot/requirements.txt b/voicebot/requirements.txt index f0fa39f..bd6d0fa 100644 --- a/voicebot/requirements.txt +++ b/voicebot/requirements.txt @@ -13,15 +13,15 @@ audioread==3.0.1 autograd==1.8.0 av==14.4.0 brotli==1.1.0 -certifi==2022.12.7 +certifi==2025.8.3 cffi==1.17.1 -charset-normalizer==2.1.1 +charset-normalizer==3.4.3 click==8.2.1 cma==4.3.0 contourpy==1.3.3 cryptography==45.0.7 cycler==0.12.1 -datasets==2.19.1 +datasets==4.0.0 decorator==5.2.1 deprecated==1.2.18 dill==0.3.8 @@ -31,7 +31,7 @@ ffmpy==0.6.1 filelock==3.13.1 fonttools==4.59.2 frozenlist==1.7.0 -fsspec==2024.2.0 +fsspec==2024.6.1 google-crc32c==1.7.1 gradio==5.44.1 gradio-client==1.12.1 @@ -42,7 +42,7 @@ hf-xet==1.1.9 httpcore==1.0.9 httpx==0.28.1 huggingface-hub==0.34.4 -idna==3.4 +idna==3.10 ifaddr==0.2.0 jinja2==3.1.4 jiwer==4.0.0 @@ -70,12 +70,12 @@ numba==0.61.2 numpy==2.2.6 onnx==1.19.0 opencv-python==4.11.0.86 -openvino==2025.2.0 +openvino==2025.3.0 openvino-telemetry==2025.2.0 optimum==1.27.0 -optimum-intel @ git+https://github.com/huggingface/optimum-intel.git@cd0028599d65e841d6fe16d4d9ccd5528f89f3cb +optimum-intel @ git+https://github.com/huggingface/optimum-intel.git@c35534d077dddf9382c6d8699f13412d28b19853 orjson==3.11.3 -packaging==24.1 +packaging==25.0 pandas==2.2.3 pillow==11.3.0 platformdirs==4.4.0 @@ -84,7 +84,6 @@ propcache==0.3.2 protobuf==6.32.0 psutil==7.0.0 pyarrow==21.0.0 -pyarrow-hotfix==0.7 pycparser==2.22 pydantic==2.11.7 pydantic-core==2.33.2 @@ -103,7 +102,7 @@ pyyaml==6.0.2 rapidfuzz==3.14.0 referencing==0.36.2 regex==2025.9.1 -requests==2.28.1 +requests==2.32.5 rich==14.1.0 rpds-py==0.27.1 ruff==0.12.11 @@ -131,7 +130,7 @@ typer==0.17.3 typing-extensions==4.15.0 typing-inspection==0.4.1 tzdata==2025.2 -urllib3==1.26.13 +urllib3==2.5.0 uvicorn==0.35.0 websockets==15.0.1 wrapt==1.17.3 diff --git a/voicebot/synthetic_media.py b/voicebot/synthetic_media.py index a2c4153..a69503b 100644 --- a/voicebot/synthetic_media.py +++ b/voicebot/synthetic_media.py @@ -13,7 +13,8 @@ import time import random from av.audio.frame import AudioFrame from asyncio import Queue, create_task, sleep -from typing import TypedDict, TYPE_CHECKING +from typing import TypedDict, TYPE_CHECKING, Tuple, List, Optional, Any +import numpy.typing as npt from aiortc import MediaStreamTrack from av import VideoFrame from logger import logger @@ -42,13 +43,7 @@ class BounceEvent(TypedDict): class AnimatedVideoTrack(MediaStreamTrack): - """ - Synthetic video track that generates animated content with a bouncing ball. - - Can also composite remote video tracks with edge detection overlay. - Remote video tracks are processed through Canny edge detection and blended - with the synthetic ball animation. - """ + """Animated synthetic video track (fixed, properly-indented implementation).""" kind = "video" @@ -58,117 +53,98 @@ class AnimatedVideoTrack(MediaStreamTrack): width: int = 320, height: int = 240, name: str = "", - audio_track: "SyntheticAudioTrack | None" = None, - ): + audio_track: Optional["SyntheticAudioTrack"] = None, + ) -> None: super().__init__() self.width = width self.height = height self.name = name self.clock = clock - self.fps = 15 self._next_frame_index = 0 - self.audio_track = audio_track # Reference to the audio track - self.remote_video_tracks: list[ - MediaStreamTrack - ] = [] # Store remote video tracks + self.audio_track = audio_track + self.remote_video_tracks: List[MediaStreamTrack] = [] - # Generate color from name hash (similar to JavaScript nameToColor) - self.ball_color = ( - self._name_to_color(name) if name else (0, 255, 136) - ) # Default green + self.ball_color = self._name_to_color(name) if name else (0, 255, 136) - # Ball properties ball_radius = min(width, height) * 0.06 self.ball = { "x": random.uniform(ball_radius, width - ball_radius), "y": random.uniform(ball_radius, height - ball_radius), "radius": ball_radius, - "speed_mps": 0.5, # Speed in meters per second (frame width = 1 meter) - "direction_x": random.uniform( - -1.0, 1.0 - ), # Random direction x component (-1 to 1) - "direction_y": random.uniform( - -1.0, 1.0 - ), # Random direction y component (-1 to 1) + "speed_mps": 0.5, + "direction_x": random.uniform(-1.0, 1.0), + "direction_y": random.uniform(-1.0, 1.0), } self.frame_count = 0 self._start_time = time.time() self._last_frame_time = time.time() - self.fps = 15 # Target frames per second - self._remote_latest = {} # track -> np.ndarray - self._remote_tasks: list[ - tuple[MediaStreamTrack, object, Queue[np.ndarray]] - ] = [] + self.fps = 15 - def set_ball_speed(self, speed_mps: float): - """Set the ball speed in meters per second""" + self._remote_latest: dict[MediaStreamTrack, npt.NDArray[Any]] = {} + self._remote_tasks: List[Tuple[MediaStreamTrack, object, Queue[npt.NDArray[Any]]]] = [] + + def set_ball_speed(self, speed_mps: float) -> None: self.ball["speed_mps"] = speed_mps - def add_remote_video_track(self, track: MediaStreamTrack): - """Add a remote video track to be composited with edge detection""" - if track.kind == "video": - self.remote_video_tracks.append(track) - logger.info(f"Added remote video track: {track}") - q: Queue[np.ndarray] = Queue(maxsize=1) + def add_remote_video_track(self, track: MediaStreamTrack) -> None: + if track.kind != "video": + return + self.remote_video_tracks.append(track) + logger.info("Added remote video track: %s", track) + q: Queue[npt.NDArray[Any]] = Queue(maxsize=1) - async def pump(): - while True: - frame = await track.recv() - if isinstance(frame, VideoFrame): - img: np.ndarray = frame.to_ndarray(format="bgr24") - if q.full(): + async def pump() -> None: + while True: + frame = await track.recv() + if isinstance(frame, VideoFrame): + img = frame.to_ndarray(format="bgr24") + if q.full(): + try: _ = q.get_nowait() - await q.put(img) + except Exception: + pass + await q.put(img) - t = create_task(pump()) - self._remote_tasks.append((track, t, q)) + t = create_task(pump()) + self._remote_tasks.append((track, t, q)) - def remove_remote_video_track(self, track: MediaStreamTrack): - """Remove a remote video track""" + def remove_remote_video_track(self, track: MediaStreamTrack) -> None: if track in self.remote_video_tracks: self.remote_video_tracks.remove(track) - logger.info(f"Removed remote video track: {track}") + logger.info("Removed remote video track: %s", track) - def _calculate_velocity_components(self, dt: float) -> tuple[float, float]: - dir_x, dir_y = self.ball["direction_x"], self.ball["direction_y"] - mag = np.hypot(dir_x, dir_y) + def _calculate_velocity_components(self, dt: float) -> Tuple[float, float]: + dir_x = float(self.ball["direction_x"]) + dir_y = float(self.ball["direction_y"]) + mag = math.hypot(dir_x, dir_y) if mag == 0: dir_x_norm, dir_y_norm = 1.0, 0.0 else: dir_x_norm, dir_y_norm = dir_x / mag, dir_y / mag - pixels_per_second = self.width * self.ball["speed_mps"] + pixels_per_second = self.width * float(self.ball["speed_mps"]) pixels_this_frame = pixels_per_second * dt return pixels_this_frame * dir_x_norm, pixels_this_frame * dir_y_norm - async def next_timestamp(self): - """Returns (pts, time_base) for 15 FPS video""" - pts = int(self.frame_count * (1 / 15) * 90000) + async def next_timestamp(self) -> Tuple[int, float]: + pts = int(self.frame_count * (1 / self.fps) * 90000) time_base = 1 / 90000 return pts, time_base - def _name_to_color(self, name: str) -> tuple[int, int, int]: - """Convert name to HSL color, then to RGB tuple""" - # Simple hash function (djb2) + def _name_to_color(self, name: str) -> Tuple[int, int, int]: hash_value = 5381 - for char in name: - hash_value = ((hash_value << 5) + hash_value + ord(char)) & 0xFFFFFFFF - - # Generate HSL color from hash + for ch in name: + hash_value = ((hash_value << 5) + hash_value + ord(ch)) & 0xFFFFFFFF hue = abs(hash_value) % 360 - sat = 60 + (abs(hash_value) % 30) # 60-89% - light = 45 + (abs(hash_value) % 30) # 45-74% - - # Convert HSL to RGB + sat = 60 + (abs(hash_value) % 30) + light = 45 + (abs(hash_value) % 30) h = hue / 360.0 s = sat / 100.0 lightness = light / 100.0 - c = (1 - abs(2 * lightness - 1)) * s x = c * (1 - abs((h * 6) % 2 - 1)) m = lightness - c / 2 - if h < 1 / 6: r, g, b = c, x, 0 elif h < 2 / 6: @@ -181,54 +157,42 @@ class AnimatedVideoTrack(MediaStreamTrack): r, g, b = x, 0, c else: r, g, b = c, 0, x + return int((b + m) * 255), int((g + m) * 255), int((r + m) * 255) - return ( - int((b + m) * 255), - int((g + m) * 255), - int((r + m) * 255), - ) # BGR for OpenCV - - async def recv(self): - """Generate video frames at 15 FPS""" + async def recv(self) -> VideoFrame: pts, time_base = await self.next_timestamp() - # Target timestamp for this frame (seconds since t0) + # schedule frame according to clock target_t = self._next_frame_index / self.fps now = self.clock.now() if target_t > now: await sleep(target_t - now) - # Use constant dt tied to fps (prevents physics jitter) dt = 1.0 / self.fps dx, dy = self._calculate_velocity_components(dt) - # PTS derived from frame index, not wall clock pts = int(self._next_frame_index * (90000 / self.fps)) time_base = 1 / 90000 - self._next_frame_index += 1 - # Create black background - frame_array = np.zeros((self.height, self.width, 3), dtype=np.uint8) + frame_array: npt.NDArray[np.uint8] = np.zeros((self.height, self.width, 3), dtype=np.uint8) - # Process remote video tracks with edge detection + # Blend in edge-detected remote frames if available for _track, _task, q in self._remote_tasks: try: - img: np.ndarray = q.get_nowait() + img = q.get_nowait() except Exception: continue edges = cv2.Canny(img, 100, 200) img_edges = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR) if img_edges.shape[:2] != (self.height, self.width): img_edges = cv2.resize(img_edges, (self.width, self.height)) - frame_array = cv2.addWeighted(frame_array, 0.7, img_edges, 0.3, 0.0) + frame_array = cv2.addWeighted(frame_array, 0.7, img_edges, 0.3, 0.0).astype(np.uint8) - # Update ball position + # Update ball position and handle bouncing ball = self.ball ball["x"] += dx ball["y"] += dy - - # Bounce off walls and trigger audio events bounce_occurred = False if ball["x"] + ball["radius"] >= self.width or ball["x"] - ball["radius"] <= 0: ball["direction_x"] = -ball["direction_x"] @@ -237,48 +201,21 @@ class AnimatedVideoTrack(MediaStreamTrack): ball["direction_y"] = -ball["direction_y"] bounce_occurred = True - # Trigger bounce sound if a bounce occurred - if bounce_occurred and self.audio_track: + if bounce_occurred and self.audio_track is not None: logger.info("Video: Bounce detected, triggering audio event") self.audio_track.add_bounce_event_at(self.clock.now()) - # Keep ball in bounds ball["x"] = max(ball["radius"], min(self.width - ball["radius"], ball["x"])) ball["y"] = max(ball["radius"], min(self.height - ball["radius"], ball["y"])) - # Draw ball - cv2.circle( - frame_array, - (int(ball["x"]), int(ball["y"])), - int(ball["radius"]), - self.ball_color, - -1, - ) + cv2.circle(frame_array, (int(ball["x"]), int(ball["y"])), int(ball["radius"]), self.ball_color, -1) - # Add frame counter and speed text frame_text = f"Frame: {int(time.time() * 1000) % 10000}" speed_text = f"Speed: {ball['speed_mps']:.2f} m/s" - cv2.putText( - frame_array, - frame_text, - (10, 20), - cv2.FONT_HERSHEY_SIMPLEX, - 0.5, - (255, 255, 255), - 1, - ) - cv2.putText( - frame_array, - speed_text, - (10, 40), - cv2.FONT_HERSHEY_SIMPLEX, - 0.5, - (255, 255, 255), - 1, - ) + cv2.putText(frame_array, frame_text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + cv2.putText(frame_array, speed_text, (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) - # Convert to VideoFrame - frame = VideoFrame.from_ndarray(frame_array.astype(np.uint8), format="bgr24") + frame = VideoFrame.from_ndarray(frame_array, format="bgr24") frame.pts = pts frame.time_base = fractions.Fraction(time_base).limit_denominator(1000000) diff --git a/voicebot/uv.lock b/voicebot/uv.lock index 064e0c6..b8a63e5 100644 --- a/voicebot/uv.lock +++ b/voicebot/uv.lock @@ -22,7 +22,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "aiortc", specifier = ">=1.13.0" }, - { name = "numpy", specifier = ">=2.3.2" }, + { name = "numpy", specifier = "<2.3" }, { name = "opencv-python", specifier = ">=4.11.0.86" }, { name = "pydantic", specifier = ">=2.11.7" }, { name = "websockets", specifier = ">=15.0.1" }, @@ -203,65 +203,40 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.2" +version = "2.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, - { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, - { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, - { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, - { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, - { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, - { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, - { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, - { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, - { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, - { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, - { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, - { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, - { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, - { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, - { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, - { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, - { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, - { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, - { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, - { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, - { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, - { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, - { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, - { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, - { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, - { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, - { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, - { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, - { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, ] [[package]] diff --git a/voicebot/whisper.py b/voicebot/whisper.py index 198102e..71cd167 100644 --- a/voicebot/whisper.py +++ b/voicebot/whisper.py @@ -1,6 +1,5 @@ from typing import Any import librosa -import numpy as np from logger import logger from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq @@ -37,7 +36,7 @@ pt_model: Any = AutoModelForSpeechSeq2Seq.from_pretrained(pretrained_model_name_ pt_model.eval() # type: ignore -def extract_input_features(audio_array: np.ndarray, sampling_rate: int): +def extract_input_features(audio_array: Any, sampling_rate: int) -> Any: """Extract input features from audio array and sampling rate.""" input_features = processor( audio_array, @@ -47,13 +46,22 @@ def extract_input_features(audio_array: np.ndarray, sampling_rate: int): return input_features -def load_audio_file(file_path: str) -> tuple[np.ndarray, int]: +def load_audio_file(file_path: str) -> tuple[Any, int]: """Load audio file from disk and return audio array and sampling rate.""" + # Whisper models expect 16kHz sample rate + target_sample_rate = 16000 + try: - # Load audio file using librosa - audio_array, sampling_rate = librosa.load(file_path, sr=None) - logger.info(f"Loaded audio file: {file_path}, duration: {len(audio_array)/sampling_rate:.2f}s, sample rate: {sampling_rate}Hz") - return audio_array, int(sampling_rate) # Ensure sampling_rate is int + # Load audio file using librosa and resample to target rate + audio_array, original_sampling_rate = librosa.load(file_path, sr=None) # type: ignore + logger.info(f"Loaded audio file: {file_path}, duration: {len(audio_array)/original_sampling_rate:.2f}s, original sample rate: {original_sampling_rate}Hz") # type: ignore + + # Resample if necessary + if original_sampling_rate != target_sample_rate: + audio_array = librosa.resample(audio_array, orig_sr=original_sampling_rate, target_sr=target_sample_rate) # type: ignore + logger.info(f"Resampled audio from {original_sampling_rate}Hz to {target_sample_rate}Hz") + + return audio_array, target_sample_rate # type: ignore except Exception as e: logger.error(f"Error loading audio file {file_path}: {e}") raise