Compare commits
3 Commits
7eafdf3e03
...
f9feb0b393
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9feb0b393 | ||
|
|
913d4f0335 | ||
|
|
33ae5a6f7d |
50
alembic/versions/c8f1d9e34b7a_v3_source_claim_columns.py
Normal file
50
alembic/versions/c8f1d9e34b7a_v3_source_claim_columns.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"""v3 second_brain: sources.claimed_by / claimed_at
|
||||||
|
|
||||||
|
Revision ID: c8f1d9e34b7a
|
||||||
|
Revises: 7b3e8a5c1f29
|
||||||
|
Create Date: 2026-05-25 12:30:00.000000
|
||||||
|
|
||||||
|
Adds a lightweight claim mechanism so the tower and dev sides never grab
|
||||||
|
the same row off the queue. The claim flow used by both workers is:
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
SELECT id, status, source_type
|
||||||
|
FROM second_brain.sources
|
||||||
|
WHERE <filter>
|
||||||
|
AND claimed_by IS NULL
|
||||||
|
ORDER BY ingested_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE SKIP LOCKED;
|
||||||
|
UPDATE second_brain.sources
|
||||||
|
SET claimed_by = :worker, claimed_at = now() AT TIME ZONE 'UTC'
|
||||||
|
WHERE id = :id;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
`FOR UPDATE SKIP LOCKED` is the actual race-safety primitive — the column
|
||||||
|
pair is observability (who has it, since when, for stale-claim recovery).
|
||||||
|
A partial index would help once we have a real queue depth; deliberately
|
||||||
|
not added now per Travis's "no gold-plating".
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "c8f1d9e34b7a"
|
||||||
|
down_revision: str | Sequence[str] | None = "7b3e8a5c1f29"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute("SET search_path TO second_brain")
|
||||||
|
op.execute("ALTER TABLE sources ADD COLUMN claimed_by VARCHAR(100)")
|
||||||
|
op.execute("ALTER TABLE sources ADD COLUMN claimed_at TIMESTAMP")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.execute("SET search_path TO second_brain")
|
||||||
|
op.execute("ALTER TABLE sources DROP COLUMN IF EXISTS claimed_at")
|
||||||
|
op.execute("ALTER TABLE sources DROP COLUMN IF EXISTS claimed_by")
|
||||||
167
deploy/tower/README.md
Normal file
167
deploy/tower/README.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# Tower-side transcribe worker — deploy notes
|
||||||
|
|
||||||
|
Target host: **EndeavourOS tower** (Arch base), RTX 3080. Runs the
|
||||||
|
`second-brain transcribe-worker` daemon under systemd. Reaches the
|
||||||
|
petalbrain Postgres over WireGuard at `db.wg.herbylab.dev:5432`.
|
||||||
|
|
||||||
|
This worker only does **pull + transcribe** for VIDEO sources. Articles
|
||||||
|
and all extraction/embedding stay on the dev side.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. System packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build / runtime tooling.
|
||||||
|
sudo pacman -S --needed base-devel git ffmpeg python uv
|
||||||
|
|
||||||
|
# NVIDIA — driver, CUDA, cuDNN. faster-whisper / CTranslate2 needs cuDNN
|
||||||
|
# at runtime; missing-symbol errors at first transcribe are usually a
|
||||||
|
# cuDNN install gap. Use whichever driver/cuda channel matches your
|
||||||
|
# Arch derivative; on stock EndeavourOS the AUR/community packages work:
|
||||||
|
sudo pacman -S --needed nvidia nvidia-utils cuda cudnn
|
||||||
|
|
||||||
|
# Confirm the GPU is visible.
|
||||||
|
nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
|
yt-dlp comes in via the Python project (`uv sync` step below), so the
|
||||||
|
distro yt-dlp is *optional*. Leave it off the host unless you want it on
|
||||||
|
your CLI PATH.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Project checkout + venv
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /opt/projects/homelab
|
||||||
|
sudo chown herbyadmin:herbyadmin /opt/projects/homelab
|
||||||
|
cd /opt/projects/homelab
|
||||||
|
git clone gitea-lovebug:petal-power/second-brain.git
|
||||||
|
cd second-brain
|
||||||
|
|
||||||
|
# Install the runtime deps + the `tower` extra (faster-whisper / CTranslate2).
|
||||||
|
# The dev side does NOT install --extra tower; only the tower needs it.
|
||||||
|
uv sync --extra tower
|
||||||
|
```
|
||||||
|
|
||||||
|
`faster-whisper` will pull a couple hundred MB of CUDA wheels. First-run
|
||||||
|
model download (~3 GB for large-v3) lands under `~/.cache/huggingface/`
|
||||||
|
the first time the worker transcribes; pre-warm it if you want:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python -c "from faster_whisper import WhisperModel; WhisperModel('large-v3', device='cuda', compute_type='float16')"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. WireGuard
|
||||||
|
|
||||||
|
The worker assumes the tunnel is already configured and that
|
||||||
|
`wg-quick@wg-lan.service` brings it up at boot. Topology:
|
||||||
|
|
||||||
|
| Peer | WG IP |
|
||||||
|
|------|-------|
|
||||||
|
| hub (herbydev) | `10.99.0.1` |
|
||||||
|
| tower | `10.99.0.2` |
|
||||||
|
|
||||||
|
`db.wg.herbylab.dev` resolves to `10.99.0.1`. Confirm connectivity from
|
||||||
|
the tower:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ping -c2 db.wg.herbylab.dev
|
||||||
|
nc -vz db.wg.herbylab.dev 5432
|
||||||
|
```
|
||||||
|
|
||||||
|
Tunnel setup itself is **out of scope for this README** — that's
|
||||||
|
configured separately as part of Travis's infra.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. EnvironmentFile
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -m 0600 -o root -g root \
|
||||||
|
deploy/tower/second-brain-transcribe.env.example \
|
||||||
|
/etc/default/second-brain-transcribe
|
||||||
|
sudo $EDITOR /etc/default/second-brain-transcribe # paste the lovebug password
|
||||||
|
```
|
||||||
|
|
||||||
|
The lovebug password lives in `/opt/backups/postgres-consolidation/credentials.env`
|
||||||
|
on the dev host. Copy it across through whatever channel you trust
|
||||||
|
(personal Bitwarden, scp over WG, etc.) — do not commit it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. systemd unit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -m 0644 deploy/tower/second-brain-transcribe.service \
|
||||||
|
/etc/systemd/system/second-brain-transcribe.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now second-brain-transcribe.service
|
||||||
|
```
|
||||||
|
|
||||||
|
The unit declares `After=` + `Wants=` on `wg-quick@wg-lan.service`, so
|
||||||
|
the worker starts after the tunnel comes up at boot. If the tunnel
|
||||||
|
disappears mid-run the worker keeps polling and logs DB-unreachable
|
||||||
|
warnings; it doesn't crash-loop.
|
||||||
|
|
||||||
|
### Day-to-day commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# What's it doing right now?
|
||||||
|
systemctl status second-brain-transcribe.service
|
||||||
|
journalctl -u second-brain-transcribe.service -f
|
||||||
|
|
||||||
|
# Pause without uninstalling — toggle from the dashboard at
|
||||||
|
# https://brain.herbylab.dev/settings (or whichever host).
|
||||||
|
# Or stop the unit:
|
||||||
|
sudo systemctl stop second-brain-transcribe.service
|
||||||
|
|
||||||
|
# Pick up code updates:
|
||||||
|
cd /opt/projects/homelab/second-brain
|
||||||
|
git pull
|
||||||
|
uv sync --extra tower
|
||||||
|
sudo systemctl restart second-brain-transcribe.service
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Validation — what to confirm once WG is live
|
||||||
|
|
||||||
|
These can only be verified on the tower itself:
|
||||||
|
|
||||||
|
1. **WG reachability.** `nc -vz db.wg.herbylab.dev 5432` succeeds.
|
||||||
|
2. **DB auth.** `uv run alembic current` returns the latest revision id.
|
||||||
|
3. **GPU large-v3 path.** With a short test video queued via
|
||||||
|
`second-brain add <url>` on the dev side, run `uv run second-brain transcribe-worker --once`
|
||||||
|
on the tower. Watch `journalctl` — first run pulls the model from
|
||||||
|
HuggingFace (slow), subsequent runs are fast. The source row should
|
||||||
|
end up in `TRANSCRIBED` with `transcript_text` populated and
|
||||||
|
`claimed_by` cleared.
|
||||||
|
4. **Race safety smoke test.** Run two `--once` invocations in parallel
|
||||||
|
against an empty queue, then with one PENDING video. Only one should
|
||||||
|
claim it; the other should see no work.
|
||||||
|
5. **Settings round-trip.** Flip `transcription_enabled` off on the
|
||||||
|
dashboard; within one poll cycle (~15s) the worker logs
|
||||||
|
`transcription_enabled=false — sleeping`. Flip back on, the next
|
||||||
|
PENDING video gets picked up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Follow-ups (out of scope for this round)
|
||||||
|
|
||||||
|
- **Media + subtitles directory currently lives on the tower's local
|
||||||
|
disk** (under `~/.local/share/second-brain/`). Move to a NAS mount
|
||||||
|
later so the dev side can compile/inspect the SRTs without a
|
||||||
|
cross-machine fetch. Open question on the right mount point + cred
|
||||||
|
flow — see the project's `postgres-migration-planning.md` for the
|
||||||
|
related vault-location decision Travis still owes.
|
||||||
|
- **Tower-side observability.** No metrics endpoint yet; journald is
|
||||||
|
the only signal. A `/api/worker-status` heartbeat the dashboard could
|
||||||
|
read would be nice once two workers exist.
|
||||||
|
- **GPU concurrency > 1.** `transcription_max_concurrent_gpu_jobs`
|
||||||
|
exists in `pipeline_settings` but the worker only runs one job at a
|
||||||
|
time. Scale-out would need a per-job semaphore (or just running N
|
||||||
|
worker instances with distinct `SECOND_BRAIN_WORKER_ID`s).
|
||||||
24
deploy/tower/second-brain-transcribe.env.example
Normal file
24
deploy/tower/second-brain-transcribe.env.example
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# /etc/default/second-brain-transcribe
|
||||||
|
#
|
||||||
|
# Copy to /etc/default/second-brain-transcribe, chmod 0600, chown root:root
|
||||||
|
# (systemd reads it before dropping to User=herbyadmin so a non-readable
|
||||||
|
# permissions mode is fine — the herbyadmin user never touches this file).
|
||||||
|
|
||||||
|
# REQUIRED — petalbrain Postgres reached over the WireGuard tunnel.
|
||||||
|
# Hub-side hostname is db.wg.herbylab.dev (10.99.0.1); the tower is 10.99.0.2.
|
||||||
|
# No TLS — WireGuard already encrypts, and lovebug doesn't require SSL.
|
||||||
|
SECOND_BRAIN_DATABASE_URL=postgresql+psycopg://lovebug:REPLACE_ME@db.wg.herbylab.dev:5432/petalbrain
|
||||||
|
|
||||||
|
# OPTIONAL — only set if the tower has a non-standard whisper layout.
|
||||||
|
# Defaults from settings.toml: model=large-v3, device=cuda, compute_type=float16.
|
||||||
|
# WHISPER_MODEL=large-v3
|
||||||
|
# WHISPER_DEVICE=cuda
|
||||||
|
# WHISPER_COMPUTE_TYPE=float16
|
||||||
|
|
||||||
|
# OPTIONAL — identifier stamped into sources.claimed_by (default tower:<hostname>).
|
||||||
|
# Useful if you ever run multiple tower workers and want to tell them apart.
|
||||||
|
# SECOND_BRAIN_WORKER_ID=tower-main
|
||||||
|
|
||||||
|
# OPTIONAL — bump worker log verbosity. INFO is the default.
|
||||||
|
# PYTHONUNBUFFERED=1
|
||||||
|
# SECOND_BRAIN_LOG_LEVEL=DEBUG
|
||||||
46
deploy/tower/second-brain-transcribe.service
Normal file
46
deploy/tower/second-brain-transcribe.service
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=second-brain transcribe worker (tower-side, faster-whisper on GPU)
|
||||||
|
Documentation=https://gitea.plantbasedsoutherner.com/petal-power/second-brain
|
||||||
|
# The worker reaches petalbrain over WireGuard, so the tunnel must be up first.
|
||||||
|
# `Wants=` is the soft form — if the tunnel disappears later the worker logs
|
||||||
|
# DB-unreachable warnings and retries, it doesn't crash-loop.
|
||||||
|
After=network-online.target wg-quick@wg-lan.service
|
||||||
|
Wants=network-online.target wg-quick@wg-lan.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=herbyadmin
|
||||||
|
Group=herbyadmin
|
||||||
|
|
||||||
|
# Adjust to wherever you `git clone`d the repo on the tower.
|
||||||
|
WorkingDirectory=/opt/projects/homelab/second-brain
|
||||||
|
|
||||||
|
# Env (SECOND_BRAIN_DATABASE_URL, optional knobs). 0600, owned by herbyadmin.
|
||||||
|
EnvironmentFile=/etc/default/second-brain-transcribe
|
||||||
|
|
||||||
|
# `uv run` resolves the project venv. Keep the worker in the foreground so
|
||||||
|
# systemd owns the lifecycle; the worker handles SIGTERM/SIGINT cleanly and
|
||||||
|
# exits after the current iteration finishes.
|
||||||
|
ExecStart=/usr/bin/uv run second-brain transcribe-worker
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Crash loop guard — systemd will give up after this if the unit keeps
|
||||||
|
# dying. The worker itself logs+backs-off on DB outages so this only
|
||||||
|
# trips on genuine failure.
|
||||||
|
StartLimitIntervalSec=300
|
||||||
|
StartLimitBurst=5
|
||||||
|
|
||||||
|
# A few sandboxing nice-to-haves. Loose on purpose — the worker needs
|
||||||
|
# /dev/nvidia* for CUDA and writes to user-owned media/subtitles dirs.
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=no
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
# Stdout/stderr → journald.
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@ -10,7 +10,6 @@ dependencies = [
|
|||||||
"fastapi>=0.115.0",
|
"fastapi>=0.115.0",
|
||||||
"httpx>=0.28.0",
|
"httpx>=0.28.0",
|
||||||
"jinja2>=3.1.0",
|
"jinja2>=3.1.0",
|
||||||
"openai-whisper",
|
|
||||||
"psycopg[binary]>=3.2",
|
"psycopg[binary]>=3.2",
|
||||||
"psycopg-pool>=3.2",
|
"psycopg-pool>=3.2",
|
||||||
"pydantic>=2.0.0",
|
"pydantic>=2.0.0",
|
||||||
@ -29,6 +28,11 @@ dependencies = [
|
|||||||
# Max OAuth subscription and has no Python-side Anthropic dependency.
|
# Max OAuth subscription and has no Python-side Anthropic dependency.
|
||||||
api = ["anthropic>=0.40.0"]
|
api = ["anthropic>=0.40.0"]
|
||||||
|
|
||||||
|
# Tower-only: faster-whisper (CTranslate2) for the transcribe worker.
|
||||||
|
# Pulls in CUDA wheels — install with `uv sync --extra tower` on the
|
||||||
|
# tower box, leave it off on dev. CPU/int8 path works on this same wheel.
|
||||||
|
tower = ["faster-whisper>=1.0.0"]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest>=8.0",
|
"pytest>=8.0",
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
YouTube pull + Whisper transcribe adapter.
|
YouTube pull adapter (yt-dlp only).
|
||||||
|
|
||||||
Ported from xtract/main.py (VideoDownloader + SubtitleExtractor classes).
|
Transcription used to live here on a torch-whisper backend. It moved to
|
||||||
Adapted to work with the second-brain Source model and Config.
|
`second_brain.transcribe` (faster-whisper) once the pipeline split so the
|
||||||
|
tower owns transcription and the dev side never imports the heavy model.
|
||||||
|
|
||||||
|
This module is now safe to import from any process — yt-dlp is small, and
|
||||||
|
no GPU / whisper dependency is loaded at import time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@ -11,7 +15,6 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import whisper
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
from second_brain.config import Config
|
from second_brain.config import Config
|
||||||
@ -84,84 +87,18 @@ class VideoDownloader:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SubtitleExtractor:
|
|
||||||
"""Transcribe a video file to an SRT subtitle file using OpenAI Whisper.
|
|
||||||
|
|
||||||
Ported from xtract SubtitleExtractor — identical core logic,
|
|
||||||
writes SRTs to config.subtitles_dir.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, config: Config) -> None:
|
|
||||||
self.config = config
|
|
||||||
self._model = None
|
|
||||||
|
|
||||||
def _load_model(self) -> None:
|
|
||||||
if self._model is None:
|
|
||||||
print(f"[Whisper] Loading {self.config.whisper_model} model…")
|
|
||||||
self._model = whisper.load_model(self.config.whisper_model)
|
|
||||||
print("[Whisper] Model ready")
|
|
||||||
|
|
||||||
def extract(self, media_file: Path) -> Optional[Path]:
|
|
||||||
"""Transcribe *media_file* and return the path to the SRT file."""
|
|
||||||
self.config.subtitles_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
srt_file = self.config.subtitles_dir / f"{media_file.stem}.srt"
|
|
||||||
|
|
||||||
if srt_file.exists():
|
|
||||||
print(f"[Whisper] Already transcribed: {srt_file.name}")
|
|
||||||
return srt_file
|
|
||||||
|
|
||||||
self._load_model()
|
|
||||||
print(f"[Whisper] Transcribing: {media_file.name}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = self._model.transcribe(
|
|
||||||
str(media_file),
|
|
||||||
verbose=False,
|
|
||||||
language=None,
|
|
||||||
)
|
|
||||||
self._write_srt(result, srt_file)
|
|
||||||
print(f"[Whisper] Saved: {srt_file.name}")
|
|
||||||
return srt_file
|
|
||||||
except Exception as exc:
|
|
||||||
print(f"[Whisper] Error transcribing {media_file.name}: {exc}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def extract_text(self, media_file: Path) -> Optional[str]:
|
|
||||||
"""Return the plain-text transcript (no SRT formatting)."""
|
|
||||||
self._load_model()
|
|
||||||
try:
|
|
||||||
result = self._model.transcribe(str(media_file), verbose=False)
|
|
||||||
return " ".join(seg["text"].strip() for seg in result["segments"])
|
|
||||||
except Exception as exc:
|
|
||||||
print(f"[Whisper] Error: {exc}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _write_srt(result: dict, output_file: Path) -> None:
|
|
||||||
with open(output_file, "w", encoding="utf-8") as fh:
|
|
||||||
for i, seg in enumerate(result["segments"], start=1):
|
|
||||||
start = _fmt_timestamp(seg["start"])
|
|
||||||
end = _fmt_timestamp(seg["end"])
|
|
||||||
fh.write(f"{i}\n{start} --> {end}\n{seg['text'].strip()}\n\n")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def read_srt(srt_file: Path) -> str:
|
|
||||||
"""Read an SRT file and return its raw text content."""
|
|
||||||
return srt_file.read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# High-level adapter
|
# High-level adapter
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class YouTubeAdapter:
|
class YouTubeAdapter:
|
||||||
"""Orchestrates pull + transcribe for a YouTube Source record."""
|
"""yt-dlp pull side of a video Source. Transcription is handled by
|
||||||
|
`second_brain.transcribe.Transcriber` on the tower."""
|
||||||
|
|
||||||
def __init__(self, config: Config) -> None:
|
def __init__(self, config: Config) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
self.downloader = VideoDownloader(config)
|
self.downloader = VideoDownloader(config)
|
||||||
self.transcriber = SubtitleExtractor(config)
|
|
||||||
|
|
||||||
def pull(self, source: Source) -> bool:
|
def pull(self, source: Source) -> bool:
|
||||||
"""Download the video. Updates source in-place, returns success."""
|
"""Download the video. Updates source in-place, returns success."""
|
||||||
@ -186,38 +123,12 @@ class YouTubeAdapter:
|
|||||||
source.status = SourceStatus.FAILED
|
source.status = SourceStatus.FAILED
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def transcribe(self, source: Source) -> bool:
|
|
||||||
"""Transcribe the downloaded video. Updates source in-place."""
|
|
||||||
if not source.media_path:
|
|
||||||
source.error_message = "No media_path; run pull first"
|
|
||||||
return False
|
|
||||||
|
|
||||||
media_file = Path(source.media_path)
|
|
||||||
srt_file = self.transcriber.extract(media_file)
|
|
||||||
if srt_file is None:
|
|
||||||
source.error_message = "Whisper transcription failed"
|
|
||||||
source.status = SourceStatus.FAILED
|
|
||||||
return False
|
|
||||||
|
|
||||||
source.transcript_path = str(srt_file)
|
|
||||||
source.transcript_text = SubtitleExtractor.read_srt(srt_file)
|
|
||||||
source.status = SourceStatus.TRANSCRIBED
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _fmt_timestamp(seconds: float) -> str:
|
|
||||||
h = int(seconds // 3600)
|
|
||||||
m = int((seconds % 3600) // 60)
|
|
||||||
s = int(seconds % 60)
|
|
||||||
ms = int((seconds % 1) * 1000)
|
|
||||||
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_upload_date(upload_date: Optional[str]) -> Optional[datetime]:
|
def _parse_upload_date(upload_date: Optional[str]) -> Optional[datetime]:
|
||||||
"""Parse yt-dlp's YYYYMMDD upload_date string."""
|
"""Parse yt-dlp's YYYYMMDD upload_date string."""
|
||||||
if not upload_date or len(upload_date) != 8:
|
if not upload_date or len(upload_date) != 8:
|
||||||
@ -228,4 +139,4 @@ def _parse_upload_date(upload_date: Optional[str]) -> Optional[datetime]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["VideoDownloader", "SubtitleExtractor", "YouTubeAdapter"]
|
__all__ = ["VideoDownloader", "YouTubeAdapter"]
|
||||||
|
|||||||
154
src/second_brain/claim.py
Normal file
154
src/second_brain/claim.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
"""Queue-claim helpers shared by the tower transcribe worker, the dev-side
|
||||||
|
process command, and the dev-side extract scheduler.
|
||||||
|
|
||||||
|
The race-safety primitive is `SELECT ... FOR UPDATE SKIP LOCKED`. The
|
||||||
|
`claimed_by` / `claimed_at` columns are observability + stale-claim
|
||||||
|
recovery only — Postgres row-locks are what actually keep two machines
|
||||||
|
from grabbing the same row.
|
||||||
|
|
||||||
|
Usage pattern (worker side, simplified)::
|
||||||
|
|
||||||
|
with db.session() as sess:
|
||||||
|
source_id = claim_next_source(
|
||||||
|
sess,
|
||||||
|
worker_id="tower",
|
||||||
|
statuses=[SourceStatus.PENDING, SourceStatus.PULLED],
|
||||||
|
source_type=SourceType.VIDEO,
|
||||||
|
)
|
||||||
|
# commit drops the row lock — the claimed_by/at columns persist.
|
||||||
|
|
||||||
|
if source_id is None:
|
||||||
|
# nothing to do; sleep and retry
|
||||||
|
return
|
||||||
|
|
||||||
|
with db.session() as sess:
|
||||||
|
source = sess.get(Source, source_id)
|
||||||
|
try:
|
||||||
|
...do work, mutate status...
|
||||||
|
finally:
|
||||||
|
release_claim(sess, source_id)
|
||||||
|
|
||||||
|
The claim is *released* once the row reaches a terminal status for this
|
||||||
|
stage (TRANSCRIBED, FAILED, etc.) so a follower stage can pick it up. If
|
||||||
|
a worker dies mid-claim, `reap_stale_claims` (called at worker startup)
|
||||||
|
clears claims older than the configured grace window.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from second_brain.models import Source, SourceStatus, SourceType, utcnow
|
||||||
|
|
||||||
|
|
||||||
|
def claim_next_source(
|
||||||
|
sess: Session,
|
||||||
|
*,
|
||||||
|
worker_id: str,
|
||||||
|
statuses: Iterable[SourceStatus],
|
||||||
|
source_type: Optional[SourceType] = None,
|
||||||
|
) -> Optional[int]:
|
||||||
|
"""Atomically claim the oldest unclaimed source matching the filters.
|
||||||
|
|
||||||
|
Returns the source's id (so the caller can re-fetch it in a fresh
|
||||||
|
session if it likes), or None if no row matched. The caller's
|
||||||
|
session-level COMMIT drops the row lock — the claim survives via the
|
||||||
|
`claimed_by` / `claimed_at` columns.
|
||||||
|
"""
|
||||||
|
if not statuses:
|
||||||
|
return None
|
||||||
|
|
||||||
|
status_values = [s.value for s in statuses]
|
||||||
|
params: dict = {
|
||||||
|
"statuses": status_values,
|
||||||
|
"worker": worker_id,
|
||||||
|
"now": utcnow(),
|
||||||
|
}
|
||||||
|
type_clause = ""
|
||||||
|
if source_type is not None:
|
||||||
|
type_clause = "AND source_type = :source_type"
|
||||||
|
params["source_type"] = source_type.value
|
||||||
|
|
||||||
|
# Raw SQL because SQLAlchemy ORM doesn't expose SKIP LOCKED cleanly
|
||||||
|
# across all minor versions and we want the exact lock semantics
|
||||||
|
# documented in the module header. `status::text` lets us bind a
|
||||||
|
# parameter list against the native enum cleanly.
|
||||||
|
pick = sess.execute(
|
||||||
|
text(
|
||||||
|
f"""
|
||||||
|
SELECT id
|
||||||
|
FROM second_brain.sources
|
||||||
|
WHERE status::text = ANY(:statuses)
|
||||||
|
{type_clause}
|
||||||
|
AND claimed_by IS NULL
|
||||||
|
ORDER BY ingested_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE SKIP LOCKED
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
params,
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if pick is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
source_id = int(pick[0])
|
||||||
|
sess.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
UPDATE second_brain.sources
|
||||||
|
SET claimed_by = :worker,
|
||||||
|
claimed_at = :now
|
||||||
|
WHERE id = :id
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{"worker": worker_id, "now": params["now"], "id": source_id},
|
||||||
|
)
|
||||||
|
return source_id
|
||||||
|
|
||||||
|
|
||||||
|
def release_claim(sess: Session, source_id: int) -> None:
|
||||||
|
"""Drop the claim on a source so a follower stage can pick it up.
|
||||||
|
|
||||||
|
The work-state columns (`status`, `transcript_text`, …) are the
|
||||||
|
caller's responsibility — this just nulls the claim pair.
|
||||||
|
"""
|
||||||
|
src = sess.get(Source, source_id)
|
||||||
|
if src is None:
|
||||||
|
return
|
||||||
|
src.claimed_by = None
|
||||||
|
src.claimed_at = None
|
||||||
|
|
||||||
|
|
||||||
|
def reap_stale_claims(
|
||||||
|
sess: Session,
|
||||||
|
*,
|
||||||
|
older_than: timedelta = timedelta(minutes=30),
|
||||||
|
) -> int:
|
||||||
|
"""Clear claims older than the grace window — recovery for crashed workers.
|
||||||
|
|
||||||
|
Returns the number of rows reaped. Safe to call at worker startup;
|
||||||
|
pairs with the worker setting a short poll interval so a stuck
|
||||||
|
transcribe doesn't dam the queue forever.
|
||||||
|
"""
|
||||||
|
cutoff = utcnow() - older_than
|
||||||
|
result = sess.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
UPDATE second_brain.sources
|
||||||
|
SET claimed_by = NULL,
|
||||||
|
claimed_at = NULL
|
||||||
|
WHERE claimed_by IS NOT NULL
|
||||||
|
AND claimed_at < :cutoff
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{"cutoff": cutoff},
|
||||||
|
)
|
||||||
|
return result.rowcount or 0
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["claim_next_source", "release_claim", "reap_stale_claims"]
|
||||||
@ -29,8 +29,14 @@ _DEFAULTS: dict[str, Any] = {
|
|||||||
"vault_path": "~/Documents/second-brain-vault",
|
"vault_path": "~/Documents/second-brain-vault",
|
||||||
"media_dir": "~/.local/share/second-brain/media",
|
"media_dir": "~/.local/share/second-brain/media",
|
||||||
"subtitles_dir": "~/.local/share/second-brain/subtitles",
|
"subtitles_dir": "~/.local/share/second-brain/subtitles",
|
||||||
"whisper_model": "small",
|
|
||||||
"domains": ["development", "content", "business", "homelab"],
|
"domains": ["development", "content", "business", "homelab"],
|
||||||
|
"whisper": {
|
||||||
|
# Tower default: faster-whisper large-v3 on cuda/float16. The
|
||||||
|
# transcribe helper picks int8 automatically if the device is cpu.
|
||||||
|
"whisper_model": "large-v3",
|
||||||
|
"whisper_device": "cuda",
|
||||||
|
"whisper_compute_type": "",
|
||||||
|
},
|
||||||
"database": {
|
"database": {
|
||||||
# Empty by default — the runtime expects SECOND_BRAIN_DATABASE_URL
|
# Empty by default — the runtime expects SECOND_BRAIN_DATABASE_URL
|
||||||
# (or HERBYLAB_DATABASE_URL) in the environment. Fill this only if
|
# (or HERBYLAB_DATABASE_URL) in the environment. Fill this only if
|
||||||
@ -101,11 +107,24 @@ class Config:
|
|||||||
self._raw.get("subtitles_dir", _DEFAULTS["subtitles_dir"])
|
self._raw.get("subtitles_dir", _DEFAULTS["subtitles_dir"])
|
||||||
).expanduser()
|
).expanduser()
|
||||||
|
|
||||||
# --- extraction ---
|
# --- transcription ---
|
||||||
|
|
||||||
|
@property
|
||||||
|
def whisper(self) -> dict[str, Any]:
|
||||||
|
"""Merged [whisper] block. Env vars in `transcribe.resolve_settings`
|
||||||
|
still win over these for the tower worker."""
|
||||||
|
return _merge(_DEFAULTS["whisper"], self._raw.get("whisper", {}))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def whisper_model(self) -> str:
|
def whisper_model(self) -> str:
|
||||||
return self._raw.get("whisper_model", _DEFAULTS["whisper_model"])
|
"""Backwards-compat shortcut — older callers (and settings.toml
|
||||||
|
in the wild) reference whisper_model at the top level."""
|
||||||
|
return (
|
||||||
|
self._raw.get("whisper_model")
|
||||||
|
or self.whisper.get("whisper_model", "large-v3")
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- extraction ---
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def domains(self) -> list[str]:
|
def domains(self) -> list[str]:
|
||||||
|
|||||||
@ -104,8 +104,13 @@ def process(
|
|||||||
skip_transcribe: bool,
|
skip_transcribe: bool,
|
||||||
skip_extract: bool,
|
skip_extract: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run the pipeline on pending sources."""
|
"""Run the dev-side pipeline: article pulls + extract/embed.
|
||||||
from second_brain.adapters.youtube import YouTubeAdapter
|
|
||||||
|
Video pull + transcribe have moved to the tower-side
|
||||||
|
`transcribe-worker` daemon (faster-whisper on the 3080); this command
|
||||||
|
is article-only on the pull side, but still picks up TRANSCRIBED rows
|
||||||
|
of any source_type for the extract/embed step.
|
||||||
|
"""
|
||||||
from second_brain.adapters.article import ArticleAdapter
|
from second_brain.adapters.article import ArticleAdapter
|
||||||
from second_brain.context.assembler import ContextAssembler
|
from second_brain.context.assembler import ContextAssembler
|
||||||
from second_brain.extractor.engine import ExtractionEngine
|
from second_brain.extractor.engine import ExtractionEngine
|
||||||
@ -115,7 +120,6 @@ def process(
|
|||||||
config.ensure_dirs()
|
config.ensure_dirs()
|
||||||
db = get_database()
|
db = get_database()
|
||||||
|
|
||||||
yt_adapter = YouTubeAdapter(config)
|
|
||||||
art_adapter = ArticleAdapter(config)
|
art_adapter = ArticleAdapter(config)
|
||||||
assembler = ContextAssembler(config)
|
assembler = ContextAssembler(config)
|
||||||
|
|
||||||
@ -135,52 +139,61 @@ def process(
|
|||||||
click.echo("[process] Extraction step will be skipped.")
|
click.echo("[process] Extraction step will be skipped.")
|
||||||
skip_extract = True
|
skip_extract = True
|
||||||
|
|
||||||
|
# Dev side: pull only ARTICLE sources (videos belong to the tower
|
||||||
|
# worker) — but pick up TRANSCRIBED of any type for extract/embed.
|
||||||
with db.session() as sess:
|
with db.session() as sess:
|
||||||
query = sess.query(Source.id).filter(
|
pull_ids = []
|
||||||
Source.status.in_([
|
if not skip_pull:
|
||||||
SourceStatus.PENDING,
|
pull_ids = [
|
||||||
SourceStatus.PULLED,
|
row[0]
|
||||||
SourceStatus.TRANSCRIBED,
|
for row in sess.query(Source.id)
|
||||||
])
|
.filter(
|
||||||
|
Source.status == SourceStatus.PENDING,
|
||||||
|
Source.source_type == SourceType.ARTICLE,
|
||||||
)
|
)
|
||||||
|
.all()
|
||||||
|
]
|
||||||
|
extract_ids = [
|
||||||
|
row[0]
|
||||||
|
for row in sess.query(Source.id)
|
||||||
|
.filter(Source.status == SourceStatus.TRANSCRIBED)
|
||||||
|
.all()
|
||||||
|
]
|
||||||
|
# Keep order stable (pulls first so freshly-transcribed articles
|
||||||
|
# roll into extract in the same invocation) and de-duplicate.
|
||||||
|
source_ids: list[int] = []
|
||||||
|
for sid in pull_ids + extract_ids:
|
||||||
|
if sid not in source_ids:
|
||||||
|
source_ids.append(sid)
|
||||||
if limit:
|
if limit:
|
||||||
query = query.limit(limit)
|
source_ids = source_ids[:limit]
|
||||||
source_ids = [row[0] for row in query.all()]
|
|
||||||
|
|
||||||
if not source_ids:
|
if not source_ids:
|
||||||
click.echo("[process] No sources to process.")
|
click.echo("[process] No sources to process.")
|
||||||
return
|
return
|
||||||
|
|
||||||
click.echo(f"[process] Processing {len(source_ids)} source(s)…")
|
click.echo(f"[process] Processing {len(source_ids)} source(s)…")
|
||||||
|
if skip_transcribe:
|
||||||
|
click.echo("[process] (--skip-transcribe is now a no-op — transcription "
|
||||||
|
"runs on the tower worker.)")
|
||||||
|
|
||||||
for source_id in source_ids:
|
for source_id in source_ids:
|
||||||
with db.session() as sess:
|
with db.session() as sess:
|
||||||
source = sess.get(Source, source_id)
|
source = sess.get(Source, source_id)
|
||||||
click.echo(f"\n [{source.id}] {source.url[:80]}")
|
click.echo(f"\n [{source.id}] {source.url[:80]}")
|
||||||
|
|
||||||
# Pull step
|
# Pull step — articles only on the dev side.
|
||||||
if not skip_pull and source.status == SourceStatus.PENDING:
|
if (
|
||||||
click.echo(" → pull")
|
not skip_pull
|
||||||
if source.source_type == SourceType.VIDEO:
|
and source.status == SourceStatus.PENDING
|
||||||
ok = yt_adapter.pull(source)
|
and source.source_type == SourceType.ARTICLE
|
||||||
else:
|
):
|
||||||
|
click.echo(" → pull (article)")
|
||||||
ok = art_adapter.pull(source)
|
ok = art_adapter.pull(source)
|
||||||
if not ok:
|
if not ok:
|
||||||
click.echo(f" ✗ pull failed: {source.error_message}")
|
click.echo(f" ✗ pull failed: {source.error_message}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Transcribe step (videos only — articles come out transcribed)
|
|
||||||
if (
|
|
||||||
not skip_transcribe
|
|
||||||
and source.status == SourceStatus.PULLED
|
|
||||||
and source.source_type == SourceType.VIDEO
|
|
||||||
):
|
|
||||||
click.echo(" → transcribe")
|
|
||||||
ok = yt_adapter.transcribe(source)
|
|
||||||
if not ok:
|
|
||||||
click.echo(f" ✗ transcribe failed: {source.error_message}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Extract step
|
# Extract step
|
||||||
if not skip_extract and source.status == SourceStatus.TRANSCRIBED and engine:
|
if not skip_extract and source.status == SourceStatus.TRANSCRIBED and engine:
|
||||||
click.echo(" → extract")
|
click.echo(" → extract")
|
||||||
@ -289,6 +302,52 @@ def schedule(dry_run: bool) -> None:
|
|||||||
scheduler.run(dry_run=dry_run)
|
scheduler.run(dry_run=dry_run)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# transcribe-worker (tower-side daemon)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("transcribe-worker")
|
||||||
|
@click.option("--once", is_flag=True, default=False,
|
||||||
|
help="Process at most one source then exit (useful for ad-hoc runs / tests).")
|
||||||
|
@click.option("--poll-interval", default=None, type=int,
|
||||||
|
help="Seconds between empty-queue polls (default 15).")
|
||||||
|
@click.option("--worker-id", default=None,
|
||||||
|
help="Identifier stamped into sources.claimed_by (default tower:<hostname>).")
|
||||||
|
def transcribe_worker(
|
||||||
|
once: bool,
|
||||||
|
poll_interval: Optional[int],
|
||||||
|
worker_id: Optional[str],
|
||||||
|
) -> None:
|
||||||
|
"""Long-running tower-side worker: claim → yt-dlp pull → faster-whisper.
|
||||||
|
|
||||||
|
Reads pipeline_settings each iteration so the dashboard's enable
|
||||||
|
toggle / active-hours window / max-items-per-run / max-video-length
|
||||||
|
all take effect within one poll cycle.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from second_brain.scheduler.transcribe_worker import (
|
||||||
|
DEFAULT_POLL_INTERVAL_SECONDS,
|
||||||
|
TranscribeWorker,
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||||
|
)
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
config.ensure_dirs()
|
||||||
|
worker = TranscribeWorker(
|
||||||
|
config,
|
||||||
|
worker_id=worker_id,
|
||||||
|
poll_interval=poll_interval or DEFAULT_POLL_INTERVAL_SECONDS,
|
||||||
|
)
|
||||||
|
processed = worker.run(once=once)
|
||||||
|
click.echo(f"[transcribe-worker] exiting; processed={processed}")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -124,6 +124,10 @@ class Source(Base):
|
|||||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||||
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||||
|
|
||||||
|
# Claim mechanism — see `second_brain.claim` for the queue dance.
|
||||||
|
claimed_by: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||||
|
claimed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
extraction: Mapped[Optional["Extraction"]] = relationship(
|
extraction: Mapped[Optional["Extraction"]] = relationship(
|
||||||
"Extraction", back_populates="source", uselist=False, cascade="all, delete-orphan"
|
"Extraction", back_populates="source", uselist=False, cascade="all, delete-orphan"
|
||||||
|
|||||||
312
src/second_brain/scheduler/transcribe_worker.py
Normal file
312
src/second_brain/scheduler/transcribe_worker.py
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
"""Tower-side transcribe worker — long-running poll loop.
|
||||||
|
|
||||||
|
Runs on the EndeavourOS tower (RTX 3080) as a systemd service. Reaches
|
||||||
|
petalbrain over WireGuard at db.wg.herbylab.dev:5432.
|
||||||
|
|
||||||
|
Loop body:
|
||||||
|
1. Read pipeline_settings.
|
||||||
|
- transcription_enabled = false → sleep, retry.
|
||||||
|
- outside transcription_active_hours → sleep, retry.
|
||||||
|
- transcription_max_concurrent_gpu_jobs is consulted (default 1 =
|
||||||
|
this worker is the single in-flight job; future scale-out can fan
|
||||||
|
a per-job lock here).
|
||||||
|
2. Claim the oldest unclaimed VIDEO source in {PENDING, PULLED} via
|
||||||
|
`SELECT ... FOR UPDATE SKIP LOCKED`.
|
||||||
|
3. If PENDING → yt-dlp pull → PULLED (claim retained).
|
||||||
|
4. If PULLED → faster-whisper transcribe → TRANSCRIBED (claim released).
|
||||||
|
5. Honor transcription_max_video_length_seconds: skip & mark FAILED
|
||||||
|
with a clear error_message if the metadata's duration exceeds it.
|
||||||
|
|
||||||
|
Graceful degradation:
|
||||||
|
- DB unreachable → log at WARNING, sleep, retry. Never crash-loop.
|
||||||
|
- Whisper failure on one file → mark that source FAILED, release claim,
|
||||||
|
continue with the next one.
|
||||||
|
- SIGTERM (systemd stop) → finish the current iteration, exit cleanly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from second_brain.claim import (
|
||||||
|
claim_next_source,
|
||||||
|
reap_stale_claims,
|
||||||
|
release_claim,
|
||||||
|
)
|
||||||
|
from second_brain.config import Config
|
||||||
|
from second_brain.database import get_database
|
||||||
|
from second_brain.models import Source, SourceStatus, SourceType, utcnow
|
||||||
|
from second_brain.settings_store import get_settings, in_active_window
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Default loop pace — short so a dropped-in video starts transcribing
|
||||||
|
# right away. The worker also re-reads pipeline_settings every iteration
|
||||||
|
# so a dashboard toggle takes effect within ~POLL_INTERVAL_SECONDS.
|
||||||
|
DEFAULT_POLL_INTERVAL_SECONDS = 15
|
||||||
|
DEFAULT_DB_BACKOFF_SECONDS = 30
|
||||||
|
|
||||||
|
|
||||||
|
class TranscribeWorker:
|
||||||
|
"""The tower's transcribe poll loop."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: Config,
|
||||||
|
*,
|
||||||
|
worker_id: str | None = None,
|
||||||
|
poll_interval: int = DEFAULT_POLL_INTERVAL_SECONDS,
|
||||||
|
db_backoff: int = DEFAULT_DB_BACKOFF_SECONDS,
|
||||||
|
) -> None:
|
||||||
|
self.config = config
|
||||||
|
self.worker_id = (
|
||||||
|
worker_id
|
||||||
|
or os.environ.get("SECOND_BRAIN_WORKER_ID")
|
||||||
|
or f"tower:{socket.gethostname()}"
|
||||||
|
)
|
||||||
|
self.poll_interval = poll_interval
|
||||||
|
self.db_backoff = db_backoff
|
||||||
|
self._stop = False
|
||||||
|
|
||||||
|
# SIGTERM/SIGINT → clean shutdown after the current iteration.
|
||||||
|
signal.signal(signal.SIGTERM, self._on_signal)
|
||||||
|
signal.signal(signal.SIGINT, self._on_signal)
|
||||||
|
|
||||||
|
def _on_signal(self, signum, frame): # noqa: ARG002 — frame required by signal API
|
||||||
|
logger.info("worker received signal %s — stopping after current iteration", signum)
|
||||||
|
self._stop = True
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Top-level run loop
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def run(self, *, once: bool = False, max_iterations: int | None = None) -> int:
|
||||||
|
"""Drive the loop. Returns the count of sources processed in this run.
|
||||||
|
|
||||||
|
`once=True` runs a single iteration regardless of queue depth —
|
||||||
|
useful for ad-hoc CLI invocation. `max_iterations` caps loops so
|
||||||
|
tests don't run forever.
|
||||||
|
"""
|
||||||
|
processed = 0
|
||||||
|
iterations = 0
|
||||||
|
startup_reaped = False
|
||||||
|
|
||||||
|
while not self._stop:
|
||||||
|
iterations += 1
|
||||||
|
if max_iterations is not None and iterations > max_iterations:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_database()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("DB not configured: %s — sleeping %ds", exc, self.db_backoff)
|
||||||
|
if once:
|
||||||
|
return processed
|
||||||
|
self._sleep(self.db_backoff)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
with db.session() as sess:
|
||||||
|
if not startup_reaped:
|
||||||
|
reaped = reap_stale_claims(sess)
|
||||||
|
if reaped:
|
||||||
|
logger.info("reaped %d stale claim(s) on startup", reaped)
|
||||||
|
startup_reaped = True
|
||||||
|
|
||||||
|
settings = get_settings(sess)
|
||||||
|
enabled = settings.transcription_enabled
|
||||||
|
window = (
|
||||||
|
settings.transcription_active_hours_start,
|
||||||
|
settings.transcription_active_hours_end,
|
||||||
|
)
|
||||||
|
max_items = int(settings.transcription_max_items_per_run or 0)
|
||||||
|
max_video_length = settings.transcription_max_video_length_seconds
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("DB unreachable: %s — sleeping %ds", exc, self.db_backoff)
|
||||||
|
if once:
|
||||||
|
return processed
|
||||||
|
self._sleep(self.db_backoff)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not enabled:
|
||||||
|
logger.info("transcription_enabled=false — sleeping")
|
||||||
|
if once:
|
||||||
|
return processed
|
||||||
|
self._sleep(self.poll_interval)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not in_active_window(datetime.now().time(), window[0], window[1]):
|
||||||
|
logger.info("outside transcription active window — sleeping")
|
||||||
|
if once:
|
||||||
|
return processed
|
||||||
|
self._sleep(self.poll_interval)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if max_items and processed >= max_items:
|
||||||
|
logger.info("reached transcription_max_items_per_run=%d — exiting", max_items)
|
||||||
|
break
|
||||||
|
|
||||||
|
handled = self._process_one(max_video_length)
|
||||||
|
if handled is None:
|
||||||
|
# Nothing to claim — sleep and re-poll.
|
||||||
|
if once:
|
||||||
|
return processed
|
||||||
|
self._sleep(self.poll_interval)
|
||||||
|
continue
|
||||||
|
|
||||||
|
processed += 1
|
||||||
|
if once:
|
||||||
|
return processed
|
||||||
|
|
||||||
|
logger.info("worker stopping (processed=%d, iterations=%d)", processed, iterations)
|
||||||
|
return processed
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Per-source step
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _process_one(self, max_video_length: int | None) -> int | None:
|
||||||
|
"""Try to claim and advance one video source. Returns the source id
|
||||||
|
on success, or None if nothing was claimable.
|
||||||
|
"""
|
||||||
|
db = get_database()
|
||||||
|
|
||||||
|
# Step 1: claim. Short transaction, FOR UPDATE SKIP LOCKED inside.
|
||||||
|
with db.session() as sess:
|
||||||
|
source_id = claim_next_source(
|
||||||
|
sess,
|
||||||
|
worker_id=self.worker_id,
|
||||||
|
statuses=[SourceStatus.PENDING, SourceStatus.PULLED],
|
||||||
|
source_type=SourceType.VIDEO,
|
||||||
|
)
|
||||||
|
|
||||||
|
if source_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Step 2: do the work. Fresh session because we crossed a commit.
|
||||||
|
try:
|
||||||
|
with db.session() as sess:
|
||||||
|
source = sess.get(Source, source_id)
|
||||||
|
if source is None:
|
||||||
|
return None
|
||||||
|
logger.info("[%s] starting %s id=%d url=%s",
|
||||||
|
self.worker_id, source.status.value, source.id, source.url)
|
||||||
|
self._advance(source, max_video_length, sess)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("unexpected error on source %s: %s", source_id, exc)
|
||||||
|
with db.session() as sess:
|
||||||
|
src = sess.get(Source, source_id)
|
||||||
|
if src is not None:
|
||||||
|
src.status = SourceStatus.FAILED
|
||||||
|
src.error_message = f"transcribe worker crashed: {exc}"
|
||||||
|
src.updated_at = utcnow()
|
||||||
|
release_claim(sess, source_id)
|
||||||
|
return source_id
|
||||||
|
|
||||||
|
def _advance(
|
||||||
|
self,
|
||||||
|
source: Source,
|
||||||
|
max_video_length: int | None,
|
||||||
|
sess,
|
||||||
|
) -> None:
|
||||||
|
"""Advance a single claimed source through pull → transcribe."""
|
||||||
|
from second_brain.adapters.youtube import YouTubeAdapter
|
||||||
|
from second_brain.transcribe import Transcriber, resolve_settings, write_srt
|
||||||
|
|
||||||
|
# Length guard — relies on yt-dlp's metadata for PENDING sources;
|
||||||
|
# for already-PULLED sources we trust whatever the prior pull
|
||||||
|
# recorded on the row.
|
||||||
|
if source.status == SourceStatus.PENDING:
|
||||||
|
yt = YouTubeAdapter(self.config)
|
||||||
|
try:
|
||||||
|
meta = yt.downloader.extract_metadata(source.url)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("metadata fetch failed for source %s: %s", source.id, exc)
|
||||||
|
meta = {}
|
||||||
|
duration = meta.get("duration_seconds")
|
||||||
|
if (
|
||||||
|
max_video_length is not None
|
||||||
|
and duration is not None
|
||||||
|
and duration > max_video_length
|
||||||
|
):
|
||||||
|
source.error_message = (
|
||||||
|
f"video length {duration}s exceeds "
|
||||||
|
f"transcription_max_video_length_seconds={max_video_length}"
|
||||||
|
)
|
||||||
|
source.status = SourceStatus.FAILED
|
||||||
|
source.updated_at = utcnow()
|
||||||
|
release_claim(sess, source.id)
|
||||||
|
logger.info("skipping source %s — too long (%ss)", source.id, duration)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pre-fill the metadata so we don't re-hit yt-dlp during pull.
|
||||||
|
if not source.title:
|
||||||
|
source.title = meta.get("title")
|
||||||
|
if not source.author:
|
||||||
|
source.author = meta.get("author")
|
||||||
|
if not source.duration_seconds:
|
||||||
|
source.duration_seconds = duration
|
||||||
|
if not source.published_at:
|
||||||
|
source.published_at = meta.get("published_at")
|
||||||
|
|
||||||
|
ok = yt.pull(source)
|
||||||
|
if not ok:
|
||||||
|
source.updated_at = utcnow()
|
||||||
|
release_claim(sess, source.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Transcribe.
|
||||||
|
if source.status == SourceStatus.PULLED:
|
||||||
|
if not source.media_path:
|
||||||
|
source.error_message = "no media_path after pull"
|
||||||
|
source.status = SourceStatus.FAILED
|
||||||
|
source.updated_at = utcnow()
|
||||||
|
release_claim(sess, source.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
wh = resolve_settings(self.config.whisper)
|
||||||
|
transcriber = Transcriber(
|
||||||
|
model=wh["model"],
|
||||||
|
device=wh["device"],
|
||||||
|
compute_type=wh["compute_type"],
|
||||||
|
)
|
||||||
|
media_file = Path(source.media_path)
|
||||||
|
srt_path = self.config.subtitles_dir / f"{media_file.stem}.srt"
|
||||||
|
|
||||||
|
try:
|
||||||
|
text, segments = transcriber.transcribe(media_file)
|
||||||
|
except Exception as exc:
|
||||||
|
source.error_message = f"transcribe failed: {exc}"
|
||||||
|
source.status = SourceStatus.FAILED
|
||||||
|
source.updated_at = utcnow()
|
||||||
|
release_claim(sess, source.id)
|
||||||
|
logger.exception("transcribe failed for source %s", source.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
write_srt(segments, srt_path)
|
||||||
|
source.transcript_path = str(srt_path)
|
||||||
|
source.transcript_text = text
|
||||||
|
source.status = SourceStatus.TRANSCRIBED
|
||||||
|
source.updated_at = utcnow()
|
||||||
|
release_claim(sess, source.id)
|
||||||
|
logger.info("transcribed source %s — %d chars", source.id, len(text))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _sleep(self, seconds: int) -> None:
|
||||||
|
"""Sleep that wakes early on SIGTERM."""
|
||||||
|
end = time.time() + seconds
|
||||||
|
while time.time() < end and not self._stop:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["TranscribeWorker", "DEFAULT_POLL_INTERVAL_SECONDS"]
|
||||||
171
src/second_brain/transcribe.py
Normal file
171
src/second_brain/transcribe.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
"""Audio/video transcription via faster-whisper (CTranslate2 backend).
|
||||||
|
|
||||||
|
Lazy-imports `faster_whisper` so the dev side — which doesn't transcribe
|
||||||
|
anything — doesn't need the heavyweight CUDA wheels installed.
|
||||||
|
|
||||||
|
Config keys (read from `[whisper]` in settings.toml, env-overrides win):
|
||||||
|
- `whisper_model` : the model name. Default "large-v3".
|
||||||
|
- `whisper_device` : "cuda" | "cpu" | "auto". Default "cuda".
|
||||||
|
- `whisper_compute_type` : "float16" | "int8" | "int8_float16" | …
|
||||||
|
Default float16 on cuda, int8 on cpu.
|
||||||
|
|
||||||
|
Outputs an SRT file next to `config.subtitles_dir/{stem}.srt` plus a
|
||||||
|
plain-text transcript string. The pipeline writes the text into
|
||||||
|
`sources.transcript_text` and the path into `sources.transcript_path`,
|
||||||
|
matching the previous torch-whisper contract so downstream extract/embed
|
||||||
|
doesn't need to change.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Config resolution
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve(cfg: dict | None, env_key: str, cfg_key: str, default: str) -> str:
|
||||||
|
env = os.environ.get(env_key)
|
||||||
|
if env:
|
||||||
|
return env
|
||||||
|
if cfg is not None:
|
||||||
|
v = cfg.get(cfg_key)
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_settings(config_block: dict | None = None) -> dict[str, str]:
|
||||||
|
"""Resolve whisper runtime settings from env > config block > defaults.
|
||||||
|
|
||||||
|
Pulls model/device/compute_type and picks a sane compute_type per device
|
||||||
|
when none was specified. Returns plain strings so the caller can stash
|
||||||
|
them in logs / pass straight to faster-whisper.
|
||||||
|
"""
|
||||||
|
model = _resolve(config_block, "WHISPER_MODEL", "whisper_model", "large-v3")
|
||||||
|
device = _resolve(config_block, "WHISPER_DEVICE", "whisper_device", "cuda")
|
||||||
|
|
||||||
|
explicit_compute = None
|
||||||
|
if config_block is not None:
|
||||||
|
explicit_compute = config_block.get("whisper_compute_type")
|
||||||
|
explicit_compute = os.environ.get("WHISPER_COMPUTE_TYPE", explicit_compute)
|
||||||
|
|
||||||
|
if explicit_compute:
|
||||||
|
compute_type = explicit_compute
|
||||||
|
elif device == "cpu":
|
||||||
|
compute_type = "int8"
|
||||||
|
else:
|
||||||
|
compute_type = "float16"
|
||||||
|
|
||||||
|
return {"model": model, "device": device, "compute_type": compute_type}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Transcriber
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class Transcriber:
|
||||||
|
"""faster-whisper wrapper. Model is loaded lazily on first transcribe."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model: str = "large-v3",
|
||||||
|
device: str = "cuda",
|
||||||
|
compute_type: str = "float16",
|
||||||
|
) -> None:
|
||||||
|
self.model = model
|
||||||
|
self.device = device
|
||||||
|
self.compute_type = compute_type
|
||||||
|
self._impl = None
|
||||||
|
|
||||||
|
def _load(self) -> None:
|
||||||
|
if self._impl is not None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from faster_whisper import WhisperModel # type: ignore[import-not-found]
|
||||||
|
except ImportError as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
"faster-whisper is not installed. On the tower run: "
|
||||||
|
"uv sync --extra tower"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"loading faster-whisper model=%s device=%s compute_type=%s",
|
||||||
|
self.model,
|
||||||
|
self.device,
|
||||||
|
self.compute_type,
|
||||||
|
)
|
||||||
|
self._impl = WhisperModel(
|
||||||
|
self.model, device=self.device, compute_type=self.compute_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def transcribe(
|
||||||
|
self,
|
||||||
|
media_file: Path,
|
||||||
|
*,
|
||||||
|
language: Optional[str] = None,
|
||||||
|
beam_size: int = 5,
|
||||||
|
) -> tuple[str, list[dict]]:
|
||||||
|
"""Transcribe `media_file`. Returns (full_text, segments).
|
||||||
|
|
||||||
|
Each segment dict has start/end (float seconds) and text (str) —
|
||||||
|
same shape the SRT writer needs.
|
||||||
|
"""
|
||||||
|
self._load()
|
||||||
|
assert self._impl is not None # for the type-checker
|
||||||
|
|
||||||
|
segments_iter, info = self._impl.transcribe(
|
||||||
|
str(media_file),
|
||||||
|
language=language,
|
||||||
|
beam_size=beam_size,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"transcribe start: %s language=%s duration=%.1fs",
|
||||||
|
media_file.name,
|
||||||
|
getattr(info, "language", "?"),
|
||||||
|
getattr(info, "duration", 0.0) or 0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
segments: list[dict] = []
|
||||||
|
text_parts: list[str] = []
|
||||||
|
for seg in segments_iter:
|
||||||
|
t = (seg.text or "").strip()
|
||||||
|
segments.append({"start": float(seg.start), "end": float(seg.end), "text": t})
|
||||||
|
if t:
|
||||||
|
text_parts.append(t)
|
||||||
|
|
||||||
|
return " ".join(text_parts), segments
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# SRT writer
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def write_srt(segments: Iterable[dict], output: Path) -> None:
|
||||||
|
"""Write a list of {start,end,text} dicts as SRT into `output`."""
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(output, "w", encoding="utf-8") as fh:
|
||||||
|
for i, seg in enumerate(segments, start=1):
|
||||||
|
start = _fmt_timestamp(seg["start"])
|
||||||
|
end = _fmt_timestamp(seg["end"])
|
||||||
|
fh.write(f"{i}\n{start} --> {end}\n{seg['text']}\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _fmt_timestamp(seconds: float) -> str:
|
||||||
|
h = int(seconds // 3600)
|
||||||
|
m = int((seconds % 3600) // 60)
|
||||||
|
s = int(seconds % 60)
|
||||||
|
ms = int((seconds % 1) * 1000)
|
||||||
|
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Transcriber", "resolve_settings", "write_srt"]
|
||||||
107
tests/test_claim.py
Normal file
107
tests/test_claim.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"""Live-DB test: claim_next_source is race-safe against concurrent callers.
|
||||||
|
|
||||||
|
Skips when no DB URL is configured. This is the integration-level guard
|
||||||
|
for the SELECT ... FOR UPDATE SKIP LOCKED dance — unit-testing the SQL
|
||||||
|
without Postgres would prove nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def _db_reachable() -> bool:
|
||||||
|
url = os.environ.get("SECOND_BRAIN_DATABASE_URL") or os.environ.get(
|
||||||
|
"HERBYLAB_DATABASE_URL"
|
||||||
|
)
|
||||||
|
if not url:
|
||||||
|
return False
|
||||||
|
raw = url.replace("postgresql+psycopg://", "postgresql://", 1)
|
||||||
|
try:
|
||||||
|
with psycopg.connect(raw, connect_timeout=2):
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(
|
||||||
|
not _db_reachable(),
|
||||||
|
reason="claim race test needs SECOND_BRAIN_DATABASE_URL pointed at a real petalbrain",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_two_workers_never_grab_the_same_row():
|
||||||
|
from second_brain.claim import claim_next_source, release_claim
|
||||||
|
from second_brain.database import Database, reset_database_singleton
|
||||||
|
from second_brain.models import Source, SourceStatus, SourceType, utcnow
|
||||||
|
|
||||||
|
reset_database_singleton()
|
||||||
|
db = Database()
|
||||||
|
|
||||||
|
# Seed a single TRANSCRIBED-stage candidate.
|
||||||
|
unique = uuid.uuid4().hex[:8]
|
||||||
|
url = f"https://example.test/claim-race/{unique}"
|
||||||
|
with db.session() as sess:
|
||||||
|
src = Source(
|
||||||
|
url=url,
|
||||||
|
title=f"claim-race-{unique}",
|
||||||
|
domain="development",
|
||||||
|
source_type=SourceType.VIDEO,
|
||||||
|
status=SourceStatus.PULLED,
|
||||||
|
ingested_at=utcnow(),
|
||||||
|
)
|
||||||
|
sess.add(src)
|
||||||
|
sess.flush()
|
||||||
|
source_id = src.id
|
||||||
|
|
||||||
|
results: list[int | None] = []
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
def worker(name: str):
|
||||||
|
with db.session() as sess:
|
||||||
|
claimed = claim_next_source(
|
||||||
|
sess,
|
||||||
|
worker_id=name,
|
||||||
|
statuses=[SourceStatus.PENDING, SourceStatus.PULLED],
|
||||||
|
source_type=SourceType.VIDEO,
|
||||||
|
)
|
||||||
|
with lock:
|
||||||
|
results.append(claimed)
|
||||||
|
|
||||||
|
t1 = threading.Thread(target=worker, args=("worker-A",))
|
||||||
|
t2 = threading.Thread(target=worker, args=("worker-B",))
|
||||||
|
t1.start()
|
||||||
|
t2.start()
|
||||||
|
t1.join()
|
||||||
|
t2.join()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Exactly one worker should have grabbed the row; the other gets None.
|
||||||
|
# (SKIP LOCKED guarantees this even if both transactions overlap.)
|
||||||
|
non_none = [r for r in results if r is not None]
|
||||||
|
assert non_none == [source_id], (
|
||||||
|
f"expected exactly one claimant of {source_id}, got results={results}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Confirm claim columns landed.
|
||||||
|
with db.session() as sess:
|
||||||
|
row = sess.get(Source, source_id)
|
||||||
|
assert row.claimed_by in {"worker-A", "worker-B"}
|
||||||
|
assert row.claimed_at is not None
|
||||||
|
|
||||||
|
# Releasing the claim should null both columns.
|
||||||
|
with db.session() as sess:
|
||||||
|
release_claim(sess, source_id)
|
||||||
|
with db.session() as sess:
|
||||||
|
row = sess.get(Source, source_id)
|
||||||
|
assert row.claimed_by is None
|
||||||
|
assert row.claimed_at is None
|
||||||
|
finally:
|
||||||
|
with db.session() as sess:
|
||||||
|
sess.query(Source).filter(Source.id == source_id).delete()
|
||||||
|
reset_database_singleton()
|
||||||
137
tests/test_transcribe.py
Normal file
137
tests/test_transcribe.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"""Unit + integration coverage for second_brain.transcribe.
|
||||||
|
|
||||||
|
Unit-level: resolve_settings + write_srt are pure-Python and run anywhere.
|
||||||
|
|
||||||
|
Integration: an end-to-end CPU/int8 transcribe of a synthesized audio
|
||||||
|
clip — auto-skipped when ffmpeg or faster-whisper is missing (the dev
|
||||||
|
side doesn't install faster-whisper by default). Only the *tower* with
|
||||||
|
GPU large-v3 can validate the production hot path; this test proves the
|
||||||
|
wiring + the SRT contract + the lazy-import path on cheap CPU.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Pure helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_settings_picks_int8_for_cpu_by_default():
|
||||||
|
from second_brain.transcribe import resolve_settings
|
||||||
|
|
||||||
|
out = resolve_settings({"whisper_device": "cpu"})
|
||||||
|
assert out["device"] == "cpu"
|
||||||
|
assert out["compute_type"] == "int8"
|
||||||
|
assert out["model"] == "large-v3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_settings_picks_float16_for_cuda_by_default():
|
||||||
|
from second_brain.transcribe import resolve_settings
|
||||||
|
|
||||||
|
out = resolve_settings({"whisper_device": "cuda"})
|
||||||
|
assert out["device"] == "cuda"
|
||||||
|
assert out["compute_type"] == "float16"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_settings_env_overrides_block(monkeypatch):
|
||||||
|
from second_brain.transcribe import resolve_settings
|
||||||
|
|
||||||
|
monkeypatch.setenv("WHISPER_MODEL", "small")
|
||||||
|
monkeypatch.setenv("WHISPER_DEVICE", "cpu")
|
||||||
|
monkeypatch.setenv("WHISPER_COMPUTE_TYPE", "int8_float16")
|
||||||
|
out = resolve_settings({"whisper_device": "cuda"})
|
||||||
|
assert out["model"] == "small"
|
||||||
|
assert out["device"] == "cpu"
|
||||||
|
assert out["compute_type"] == "int8_float16"
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_srt_roundtrip(tmp_path):
|
||||||
|
from second_brain.transcribe import write_srt
|
||||||
|
|
||||||
|
segs = [
|
||||||
|
{"start": 0.0, "end": 1.5, "text": "hello"},
|
||||||
|
{"start": 1.5, "end": 3.25, "text": "world"},
|
||||||
|
]
|
||||||
|
out = tmp_path / "x.srt"
|
||||||
|
write_srt(segs, out)
|
||||||
|
body = out.read_text()
|
||||||
|
assert "1\n00:00:00,000 --> 00:00:01,500\nhello" in body
|
||||||
|
assert "2\n00:00:01,500 --> 00:00:03,250\nworld" in body
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Integration: synthesized audio → CPU/int8 faster-whisper
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _have_faster_whisper() -> bool:
|
||||||
|
try:
|
||||||
|
import faster_whisper # type: ignore[import-not-found] # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _have_numpy() -> bool:
|
||||||
|
try:
|
||||||
|
import numpy # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not (_have_faster_whisper() and _have_numpy()),
|
||||||
|
reason="needs faster-whisper (uv sync --extra tower) + numpy to exercise CPU path",
|
||||||
|
)
|
||||||
|
def test_cpu_transcribe_smoke(tmp_path):
|
||||||
|
"""Hand faster-whisper a synthesized audio array on CPU/int8 with `tiny`.
|
||||||
|
|
||||||
|
Pure-tone input gives empty / minimal recognition — the point is the
|
||||||
|
wiring: model loads, the segments iterator drains, an SRT is writable.
|
||||||
|
Using `tiny` to keep this under ~30s coldcache. Skipped on dev where
|
||||||
|
faster-whisper isn't installed; only the tower's `uv sync --extra
|
||||||
|
tower` brings it in.
|
||||||
|
|
||||||
|
Bypassing ffmpeg by passing a numpy array — faster-whisper.transcribe
|
||||||
|
accepts (path | file-like | np.ndarray). This exercises the same code
|
||||||
|
path the worker uses, only with zero external binaries.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from second_brain.transcribe import write_srt
|
||||||
|
|
||||||
|
# Build 2s of 440Hz mono at 16kHz, the model's native sample rate.
|
||||||
|
sr = 16000
|
||||||
|
duration = 2.0
|
||||||
|
t = np.arange(0, int(sr * duration)) / sr
|
||||||
|
audio = (0.2 * np.sin(2 * np.pi * 440 * t)).astype(np.float32)
|
||||||
|
|
||||||
|
# Load `tiny` to keep this cheap; we're not asserting transcription
|
||||||
|
# quality, just that the pipeline executes and returns the documented
|
||||||
|
# shape.
|
||||||
|
from faster_whisper import WhisperModel # type: ignore[import-not-found]
|
||||||
|
|
||||||
|
model = WhisperModel("tiny", device="cpu", compute_type="int8")
|
||||||
|
segments_iter, _ = model.transcribe(audio, language="en", beam_size=1)
|
||||||
|
|
||||||
|
# Materialize to mirror what Transcriber.transcribe does internally.
|
||||||
|
segments: list[dict] = []
|
||||||
|
text_parts: list[str] = []
|
||||||
|
for seg in segments_iter:
|
||||||
|
s = (seg.text or "").strip()
|
||||||
|
segments.append({"start": float(seg.start), "end": float(seg.end), "text": s})
|
||||||
|
if s:
|
||||||
|
text_parts.append(s)
|
||||||
|
text = " ".join(text_parts)
|
||||||
|
|
||||||
|
assert isinstance(text, str)
|
||||||
|
assert isinstance(segments, list)
|
||||||
|
|
||||||
|
srt = tmp_path / "out.srt"
|
||||||
|
write_srt(segments, srt)
|
||||||
|
assert srt.exists()
|
||||||
611
uv.lock
generated
611
uv.lock
generated
@ -66,6 +66,30 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av"
|
||||||
|
version = "17.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/4e/f0/8c8dca97ae0cf00e8e2a53bb5cb9aca5fd484f585ef3e9b412200aff3ebd/av-17.0.1.tar.gz", hash = "sha256:fbcbd4aa43bca6a8691816283112d1659a27f407bbeb66d1397023691339f5d4", size = 4411938, upload-time = "2026-04-18T17:12:34.29Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/82/e7007dcef7bd2d2c377e2e85977701384f42d19fc808c2ccb3a99eaf58f2/av-17.0.1-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:987f4f46ceae4da6c614dcbd2b8149be9dbf680c3bb7a6841c58af9cff4d9230", size = 23238802, upload-time = "2026-04-18T17:11:51.166Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/aa/858b09a08ea6f83f91be44b5a5adad13ae8d9ac8b80fda27e73c24bfb160/av-17.0.1-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:d97f54e55b18a74912f479c1978aadd1341d38d892dee95bb5c2f2dccfa72f32", size = 18709338, upload-time = "2026-04-18T17:11:53.286Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/8b/8de3fd21c4b0b74d44337421abeab0e71462337fb6a28fff888e0c356cbd/av-17.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6eee84afa48d0e9321047cd3e4facd44b401493f6bdc753e2e1d1e7c9e6d13e", size = 34007351, upload-time = "2026-04-18T17:11:56.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/28/167b291356c2cc315a2d62a95b0ceace72b5b0bf547de30b89313110f032/av-17.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c58c71bffd9383908c85695ac61d3184c668accb04a5bd1b262e0fb8d09f60a5", size = 36345295, upload-time = "2026-04-18T17:11:59.125Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/fa/aae56f2ff2c204c408641e1120f5ca5ce9c3390cf5362245c6f1158704b5/av-17.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:42d6745d30a410ec9b22aef79a52a7ab5a001eb8f5adfd952946606a30983318", size = 35183754, upload-time = "2026-04-18T17:12:01.697Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/bd/776046f27093aef80155a204ca7d82a887ae4ee72ba4ef8411b46ea7898c/av-17.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3ed6bcd7021fe55832f95b8ef78dd01a4cb21faf3cd71f1e1bf4f20bf100b278", size = 37430809, upload-time = "2026-04-18T17:12:04.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/d5/3261bd2c6b7f6c0aa8379fc970d1ecf496330990b992ad28607785074268/av-17.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:9af524e8632a54032e361d6b88895bd3e7c6212ca560de60f5ccc525323c764c", size = 28889649, upload-time = "2026-04-18T17:12:07.04Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/39/381104e427a0c7231d2ec0d25d538d58fc20fc0458846b95860d3ef8073b/av-17.0.1-cp311-abi3-win_arm64.whl", hash = "sha256:50e58a473d65ea29b645e45c9fd8518a6783737135683ecc40571a91592bdfe4", size = 21918412, upload-time = "2026-04-18T17:12:09.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/8c/bb1498f031abb6157b30b7fc2379359176953821b6ba59fbd89dbb56f61f/av-17.0.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:1d33871742d1e71562db3c8e752cacc5a62766d7efc3ae408bff1c3e26ebb46e", size = 23484157, upload-time = "2026-04-18T17:12:11.67Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/58/dedaef187b797243cd5762722e376c69c5ad95ab23db44127f09afc2cd66/av-17.0.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1229e879f4b6431bc00f69d7f8891fe9a683b0a6e0e009e6c98eb7e449f0383d", size = 18920872, upload-time = "2026-04-18T17:12:14.826Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/26/5c550231651d6285e6a5c4f6f4a0e67459bfe2b622a7c9352be8cca8c819/av-17.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4744837f4116964280bcc72285e3cdd51361e98a696205aadd924203440ef511", size = 37471077, upload-time = "2026-04-18T17:12:17.349Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/e4/9807b89a9d775c6f015677996c48bce48aaff70b5d95885adf39e59832a2/av-17.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3d0a7d45d9599bf9df9f8249827113d4f36df1cd6b5356227b997f0552dbc98e", size = 39566981, upload-time = "2026-04-18T17:12:19.942Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/72/a22a657abc3de652f5b4f46cbbebdf7cba629752112791b81f05d340991d/av-17.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9acd0b6a6e02af2b37f63d97a03ee2c47936d58e82425c3cd075a95245937c59", size = 38397369, upload-time = "2026-04-18T17:12:22.909Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/b2/f4e83e41c1e3c186f34b7df506779d0cd7e40499e2e19519c7ece148cd20/av-17.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3d3a36204cb1f1e7691e6446afa8d6b7097b09946dae732c71c5d05ce09e506e", size = 40582445, upload-time = "2026-04-18T17:12:26.285Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/59/8676188b72eed09d48ce6cfaf0f22b0bb9f3cfd74d388ee2b7fdf960536d/av-17.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:b87b98afe971cde123953073bc9c95ab0b7efd2ecc082dd2dbd11f9d9abf190e", size = 29217136, upload-time = "2026-04-18T17:12:29.189Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/af/0a6e1d2a845988039f6c197fa7269b5e9abbe17354fb41cc9d75bb260fcb/av-17.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:a87a42c36e29f75e7dff7281944f2a6876a2c8875e225ccbf6c1ae62748b4caa", size = 22072676, upload-time = "2026-04-18T17:12:31.836Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "babel"
|
name = "babel"
|
||||||
version = "2.18.0"
|
version = "2.18.0"
|
||||||
@ -277,69 +301,35 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cuda-bindings"
|
name = "ctranslate2"
|
||||||
version = "13.2.0"
|
version = "4.7.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cuda-pathfinder" },
|
{ name = "numpy" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "setuptools" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" },
|
{ url = "https://files.pythonhosted.org/packages/68/7a/b584ea42131f0bc418fa75851f3e75fea254a22fc883e456fcf6e4c403fc/ctranslate2-4.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e8818f9211246063ae19b5efa8785519de85f87a19ffe0b270eb8d36a3d79afc", size = 1259514, upload-time = "2026-05-19T06:41:34.347Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/72/2df1d7cedec58efcc691163d810303d559468aff61e1159b487ac8d94ac1/ctranslate2-4.7.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:3df7d2123238da1b1608b691533cfc46b11e5cfda7a27598584d8866701289a9", size = 11922285, upload-time = "2026-05-19T06:41:36.587Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/11/1414218b3bca90cbbe687e834d6f0bb2b9901232f7bcce63c05e619e0083/ctranslate2-4.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84e11aa6ed8e68eb800ec5aa019830828127e1e9c0c52fe8a816a4f5a28a78a5", size = 16874916, upload-time = "2026-05-19T06:41:39.988Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" },
|
{ url = "https://files.pythonhosted.org/packages/39/9f/80cebd1ec68c102a846563deb1cae4be7be22db4a72410d8c1a958d482ad/ctranslate2-4.7.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e55d07df5298c08dc218c80d492482b732b7f0077a017602fb6fec9601c4021d", size = 39018505, upload-time = "2026-05-19T06:41:45.239Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" },
|
{ url = "https://files.pythonhosted.org/packages/fe/d0/5439a33c3844d68393f622ba8cc51d5c9b589a4d652490dbd3a4b5d8bc9b/ctranslate2-4.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:f03298e83c427db8f0f4cabb5b9b28680586dae05112651fb39794cd08f849a5", size = 18845969, upload-time = "2026-05-19T06:41:49.388Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" },
|
{ url = "https://files.pythonhosted.org/packages/3f/36/6dcfe766d4b329131c227acb982d03ceb2f5dd1509bc036a2fbb0197ebc7/ctranslate2-4.7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9152e575b226c57e677d3fecc2f2ce0825c828845ac922986382b8c37fc39740", size = 1259476, upload-time = "2026-05-19T06:41:52.082Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" },
|
{ url = "https://files.pythonhosted.org/packages/7c/74/e89361f9f84179984ca3be47bf53f2ccfb2bb0dc3c35005457bf574bf2bd/ctranslate2-4.7.2-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:eec0521ae9e790147561394257c7a95886f3d8ff28842ffd094248154a928fee", size = 11922163, upload-time = "2026-05-19T06:41:54.337Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" },
|
{ url = "https://files.pythonhosted.org/packages/11/a5/1000f55ccc1c62acb6ec798ae6c773e175fd08cbac3651e8a59c74c8014f/ctranslate2-4.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3bd9d5842716ddaed629315fb2f87139ab8e8c62364ea487fbc77c63cca49a23", size = 16875787, upload-time = "2026-05-19T06:41:58.039Z" },
|
||||||
]
|
{ url = "https://files.pythonhosted.org/packages/b9/e2/cd18090c3282bcbd2492ee129e721a25ffd3e12d2ece4a19a99091c1e87a/ctranslate2-4.7.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4d1ec8d6c9aaecb049d562bbd4358e6320b3638dcaa28ba786b2eaefd559ae6", size = 39018460, upload-time = "2026-05-19T06:42:02.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/a2/909ce8b121c0148762e49dfda71bcec9b57c502dcb68e879ba5d84370d75/ctranslate2-4.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:e329af76bbcd0c488f8a191fe6c988c3ca9fe09d3086f8469702dc906a8b4066", size = 18845965, upload-time = "2026-05-19T06:42:06.312Z" },
|
||||||
[[package]]
|
{ url = "https://files.pythonhosted.org/packages/16/f6/9aeee3b30883a4b6d2c5f2e7b8f2f5316d89eabe6eed5a62815805f1eae2/ctranslate2-4.7.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:031ce4eea0589f7e7c8b9a2ab66951d565044ed15c64f2e33af7e1ec5858dde8", size = 1260313, upload-time = "2026-05-19T06:42:09.055Z" },
|
||||||
name = "cuda-pathfinder"
|
{ url = "https://files.pythonhosted.org/packages/71/ed/96827f638a79baee9efe84d675a4b6e3c4c9337648b5edf7892a17c0a5d2/ctranslate2-4.7.2-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:8d7ede5e85acefb1b0ed18e3aa026e10a39eb4262229a058211200e2838abf02", size = 11922645, upload-time = "2026-05-19T06:42:11.41Z" },
|
||||||
version = "1.5.4"
|
{ url = "https://files.pythonhosted.org/packages/0d/b8/e42557c86a29ace602a3c0676b8ae6cfa12e52fe05fb74aa9af37d813b99/ctranslate2-4.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05127c6f04de072fba6e6e6e012f7c9265cd65cb1ad19420b861d23fb1e62e90", size = 16864569, upload-time = "2026-05-19T06:42:14.664Z" },
|
||||||
source = { registry = "https://pypi.org/simple" }
|
{ url = "https://files.pythonhosted.org/packages/50/95/39dd1f7eb4af9dd73c4b708c053a5cf170e3ff957d8950eb51f00a54184d/ctranslate2-4.7.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54392ef0f1353c8b339640ca1a342761258ea3c58e744e91562d225dc5f40ec4", size = 38984169, upload-time = "2026-05-19T06:42:19.644Z" },
|
||||||
wheels = [
|
{ url = "https://files.pythonhosted.org/packages/d8/2d/7fd9576f654f88c867fe212c53803d487f86a04efde1994f16597439bddc/ctranslate2-4.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:ea8f491f719f4e9ef38349ab2c37d7001a0e49881ee9f2193df3ddbf3aca1d4b", size = 19103583, upload-time = "2026-05-19T06:42:23.573Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl", hash = "sha256:9563d3175ce1828531acf4b94e1c1c7d67208c347ca002493e2654878b26f4b7", size = 51657, upload-time = "2026-04-27T22:42:07.712Z" },
|
{ url = "https://files.pythonhosted.org/packages/1c/96/d0fc93b0451f1fa2257744769dc85e873c4c824fe9b6364d6b2b8d034c13/ctranslate2-4.7.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2496caa4bbbaac10d81d071fa8d47ebd6a713f879e80a560797cb59767ad2a54", size = 1282921, upload-time = "2026-05-19T06:42:25.923Z" },
|
||||||
]
|
{ url = "https://files.pythonhosted.org/packages/4b/77/8b1a5290c4bd28a3ee3f9e4f72fedd08ccae96d7860b8b52fae0d0a34522/ctranslate2-4.7.2-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:b0a2065b998d14e9322a0daaa5b16f3afb23c878d443c2eb37ad1129eee80c30", size = 11943705, upload-time = "2026-05-19T06:42:28.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/e9/6ea19da08c5d1103ddfc616ce3ff2ada28d6f0c03745cc07dbed4c51d20f/ctranslate2-4.7.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f81af66bb0074b5c8d48eb5574cae2d8fc88c1af0c7377ce5ce043fef75c4317", size = 16852294, upload-time = "2026-05-19T06:42:31.234Z" },
|
||||||
[[package]]
|
{ url = "https://files.pythonhosted.org/packages/87/35/5f557ef7ed82ba1519f6cf5ccc33e2a805f9809d96bdd975a2cb9e925110/ctranslate2-4.7.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed2db340b99c1c63993459a775f667b5db35e09894b55c1035adc733ce1ae", size = 38954642, upload-time = "2026-05-19T06:42:35.798Z" },
|
||||||
name = "cuda-toolkit"
|
{ url = "https://files.pythonhosted.org/packages/3e/58/0b6d03d2da81f4f6a5f674f1934828ec72a9393d17c9f086dbb7c7c0399f/ctranslate2-4.7.2-cp314-cp314t-win_amd64.whl", hash = "sha256:76f568c6c9641cbc056f5c5c635e6ea7c927ad8ce23321804dcbbee66b2c9e24", size = 19126024, upload-time = "2026-05-19T06:42:39.605Z" },
|
||||||
version = "13.0.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
cudart = [
|
|
||||||
{ name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
cufft = [
|
|
||||||
{ name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
cufile = [
|
|
||||||
{ name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
|
|
||||||
]
|
|
||||||
cupti = [
|
|
||||||
{ name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
curand = [
|
|
||||||
{ name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
cusolver = [
|
|
||||||
{ name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
cusparse = [
|
|
||||||
{ name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
nvjitlink = [
|
|
||||||
{ name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
nvrtc = [
|
|
||||||
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
nvtx = [
|
|
||||||
{ name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -399,6 +389,22 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" },
|
{ url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "faster-whisper"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "av" },
|
||||||
|
{ name = "ctranslate2" },
|
||||||
|
{ name = "huggingface-hub" },
|
||||||
|
{ name = "onnxruntime" },
|
||||||
|
{ name = "tokenizers" },
|
||||||
|
{ name = "tqdm" },
|
||||||
|
]
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909, upload-time = "2025-10-31T11:35:47.794Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "3.29.0"
|
version = "3.29.0"
|
||||||
@ -408,6 +414,14 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
|
{ url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flatbuffers"
|
||||||
|
version = "25.12.19"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsspec"
|
name = "fsspec"
|
||||||
version = "2026.4.0"
|
version = "2026.4.0"
|
||||||
@ -481,6 +495,38 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hf-xet"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "htmldate"
|
name = "htmldate"
|
||||||
version = "1.9.4"
|
version = "1.9.4"
|
||||||
@ -554,6 +600,26 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "huggingface-hub"
|
||||||
|
version = "1.16.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "filelock" },
|
||||||
|
{ name = "fsspec" },
|
||||||
|
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "tqdm" },
|
||||||
|
{ name = "typer" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/48/0f/ed994dbade67a54407c28cab96ef845e0e6d25500be56aca6394f8bfc9dd/huggingface_hub-1.16.1.tar.gz", hash = "sha256:7f1dc4c5ec21aed69be630ad0c3378616be16f3de1a47b141c0e812965d9c832", size = 792534, upload-time = "2026-05-21T18:40:00.908Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/79/621a7dbb80c70974f73a597275351ebe03ce5bc65cb5f8f4acb5859252bc/huggingface_hub-1.16.1-py3-none-any.whl", hash = "sha256:64340de934b9ce37857ef85a82de72f5629e8a270f9119eabb12bf495eb53c22", size = 668176, upload-time = "2026-05-21T18:39:58.596Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.16"
|
version = "3.16"
|
||||||
@ -668,30 +734,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940, upload-time = "2025-02-25T20:21:44.179Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940, upload-time = "2025-02-25T20:21:44.179Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "llvmlite"
|
|
||||||
version = "0.47.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec", size = 37232771, upload-time = "2026-03-31T18:29:01.324Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb", size = 56275178, upload-time = "2026-03-31T18:29:05.748Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/da/b32cafcb926fb0ce2aa25553bf32cb8764af31438f40e2481df08884c947/llvmlite-0.47.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c6951e2b29930227963e53ee152441f0e14be92e9d4231852102d986c761e40", size = 55128632, upload-time = "2026-03-31T18:29:11.235Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e", size = 38138695, upload-time = "2026-03-31T18:29:15.43Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:de966c626c35c9dff5ae7bf12db25637738d0df83fc370cf793bc94d43d92d14", size = 37232773, upload-time = "2026-03-31T18:29:19.453Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/1d/a760e993e0c0ba6db38d46b9f48f6c7dceb8ac838824997fb9e25f97bc04/llvmlite-0.47.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddbccff2aeaff8670368340a158abefc032fe9b3ccf7d9c496639263d00151aa", size = 56275176, upload-time = "2026-03-31T18:29:24.149Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/3b/e679bc3b29127182a7f4aa2d2e9e5bea42adb93fb840484147d59c236299/llvmlite-0.47.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a7b778a2e144fc64468fb9bf509ac1226c9813a00b4d7afea5d988c4e22fca", size = 55128631, upload-time = "2026-03-31T18:29:29.536Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/f7/19e2a09c62809c9e63bbd14ce71fb92c6ff7b7b3045741bb00c781efc3c9/llvmlite-0.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:694e3c2cdc472ed2bd8bd4555ca002eec4310961dd58ef791d508f57b5cc4c94", size = 39153826, upload-time = "2026-03-31T18:29:33.681Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/a1/581a8c707b5e80efdbbe1dd94527404d33fe50bceb71f39d5a7e11bd57b7/llvmlite-0.47.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:92ec8a169a20b473c1c54d4695e371bde36489fc1efa3688e11e99beba0abf9c", size = 37232772, upload-time = "2026-03-31T18:29:37.952Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/03/16090dd6f74ba2b8b922276047f15962fbeea0a75d5601607edb301ba945/llvmlite-0.47.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbd800edd3b20bc141521f7fd45a6185a5b84109aa6855134e81397ffe72b", size = 56275178, upload-time = "2026-03-31T18:29:42.58Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/cb/0abf1dd4c5286a95ffe0c1d8c67aec06b515894a0dd2ac97f5e27b82ab0b/llvmlite-0.47.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6725179b89f03b17dabe236ff3422cb8291b4c1bf40af152826dfd34e350ae8", size = 55128632, upload-time = "2026-03-31T18:29:46.939Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/79/d3bbab197e86e0ff4f9c07122895b66a3e0d024247fcff7f12c473cb36d9/llvmlite-0.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6842cf6f707ec4be3d985a385ad03f72b2d724439e118fcbe99b2929964f0453", size = 39153839, upload-time = "2026-03-31T18:29:51.004Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lxml"
|
name = "lxml"
|
||||||
version = "6.1.1"
|
version = "6.1.1"
|
||||||
@ -801,6 +843,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" },
|
{ url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mdurl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "3.0.3"
|
version = "3.0.3"
|
||||||
@ -865,58 +919,12 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "more-itertools"
|
name = "mdurl"
|
||||||
version = "11.1.0"
|
version = "0.1.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mpmath"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "networkx"
|
|
||||||
version = "3.6.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "numba"
|
|
||||||
version = "0.65.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "llvmlite" },
|
|
||||||
{ name = "numpy" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/c5/db2ac3685833d626c0dcae6bd2330cd68433e1fd248d15f70998160d3ad7/numba-0.65.1.tar.gz", hash = "sha256:19357146c32fe9ed25059ab915e8465fb13951cf6b0aace3826b76886373ab23", size = 2765600, upload-time = "2026-04-24T02:02:56.551Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/bc/76f8f8c5cf9adee47fdb7bbb03be8900f76f902d451d7477cf12b845e1de/numba-0.65.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ac3f1e77c352dd0ea9712732c2d8f9ca507717435eec5b5013bf138ac33c4a08", size = 2681371, upload-time = "2026-04-24T02:02:26.105Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/69/47/a415af0283e4db0398104c6d1c11c9861a98dc67a7aa442a7769ed5d6196/numba-0.65.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:52bc6f3ceb8fcaff9b2ae26b4c6b1e9fee39db8d355534c0fe4f39a901246b84", size = 3802467, upload-time = "2026-04-24T02:02:27.712Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/36/246f73ec99cfeab2f2cb2ce7d4218766cc36a2da418901223f4f4da9c813/numba-0.65.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90ca10b3463bae0bd70589726fe3c77d01d6b5fc86bee54bcdf9fb6b47c28977", size = 3502628, upload-time = "2026-04-24T02:02:29.763Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/9e/3c679b2ee078425b9e99a91e44f8d132a6830d8ccce5227bc5e9181aeed8/numba-0.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:5971c632be2a2351500431f46213821dba8d02b18a9f7d02fd36bd2743e41a6a", size = 2750611, upload-time = "2026-04-24T02:02:31.477Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3", size = 2681344, upload-time = "2026-04-24T02:02:33.65Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b", size = 3810619, upload-time = "2026-04-24T02:02:36.041Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/08/e16a8b5d9a018962ebb5c66be662317cde32b9f5dab08441f90bed5522fb/numba-0.65.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:594a8680b3fadac99e97e489b1fd89007177e5336713745c3b769528c635a464", size = 3509783, upload-time = "2026-04-24T02:02:38.245Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl", hash = "sha256:85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62", size = 2750534, upload-time = "2026-04-24T02:02:39.903Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:33f5eb68eb1c843511615d14663ce60258525d6a4c65ab040e2c2b0c4cf17450", size = 2681554, upload-time = "2026-04-24T02:02:41.812Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71e73029bf53a62cc6afcf96be4bd942290d8b4c55f0a454fb536158115790f7", size = 3779602, upload-time = "2026-04-24T02:02:43.726Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/09/90/b0f09b48752d23640b8284f22aa597737e8adaddc7fbfacc4708b7f73a4c/numba-0.65.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a07635e0be926b9bdbffb09137c230fb13f6ec0e564914ba937cee12ce3eb35", size = 3479532, upload-time = "2026-04-24T02:02:45.427Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a20fcdabdefbdacf88d85caf70c3b18c4bcb7ebb8f82e6a19486383dd26ab63", size = 2752637, upload-time = "2026-04-24T02:02:47.664Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/7b/c1a341a9067367778f4152a5f01061cf281fb09582c92c510ec4918cabf6/numba-0.65.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:548dd4b3a4508d5062768d1514b2cd7b015f9a25ec7af651c50dee243965e652", size = 2684600, upload-time = "2026-04-24T02:02:49.653Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/36/98ddbcf3e4f04a6dd07e1c67249955920579ba4af6bb6868e3088f4ed282/numba-0.65.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:78abc28feff2c2ff8307fff3975b6438352759c9acb797ecd6b1fb6e7e39e31d", size = 3817198, upload-time = "2026-04-24T02:02:51.266Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/83/0dad21057ece5a835599f5d24099b091703995e23dbbf894f259e91c010b/numba-0.65.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7676cb389555805f9b9a1840cbcd1ea6c8bd5376ab6918e3a29c5ea1dbda20", size = 3533862, upload-time = "2026-04-24T02:02:52.987Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/36/8be7118ffd4c8440881046eac3d0982cc5ab42909508cf5d67024d62a2e4/numba-0.65.1-cp314-cp314t-win_amd64.whl", hash = "sha256:20609346e3bd75204950dcbbfe383a8d7dbf4902f442aedbf00f97fef4aa8f38", size = 2758237, upload-time = "2026-04-24T02:02:54.612Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -981,171 +989,36 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nvidia-cublas"
|
name = "onnxruntime"
|
||||||
version = "13.1.1.3"
|
version = "1.26.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "nvidia-cuda-nvrtc" },
|
{ name = "flatbuffers" },
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cuda-cupti"
|
|
||||||
version = "13.0.85"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cuda-nvrtc"
|
|
||||||
version = "13.0.88"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cuda-runtime"
|
|
||||||
version = "13.0.96"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cudnn-cu13"
|
|
||||||
version = "9.20.0.48"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nvidia-cublas" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cufft"
|
|
||||||
version = "12.0.0.61"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nvidia-nvjitlink" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cufile"
|
|
||||||
version = "1.15.1.6"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-curand"
|
|
||||||
version = "10.4.0.35"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cusolver"
|
|
||||||
version = "12.0.4.66"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nvidia-cublas" },
|
|
||||||
{ name = "nvidia-cusparse" },
|
|
||||||
{ name = "nvidia-nvjitlink" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cusparse"
|
|
||||||
version = "12.6.3.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nvidia-nvjitlink" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-cusparselt-cu13"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-nccl-cu13"
|
|
||||||
version = "2.29.7"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-nvjitlink"
|
|
||||||
version = "13.0.88"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-nvshmem-cu13"
|
|
||||||
version = "3.4.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nvidia-nvtx"
|
|
||||||
version = "13.0.85"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openai-whisper"
|
|
||||||
version = "20250625"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "more-itertools" },
|
|
||||||
{ name = "numba" },
|
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "tiktoken" },
|
{ name = "packaging" },
|
||||||
{ name = "torch" },
|
{ name = "protobuf" },
|
||||||
{ name = "tqdm" },
|
]
|
||||||
{ name = "triton", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or sys_platform == 'linux2'" },
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/b1/d111b1df656761f980d9e298a60039a9cb66036b1d039e777537743d0ac3/onnxruntime-1.26.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05b028781b322ad74b57ce5b50aa5280bb1fe96ceec334628ade681e0b24c1ac", size = 18016624, upload-time = "2026-05-12T00:41:01.735Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/a0/3f9d896a0385a36bd04345d6d0b802821a5782adde562e7e135f6bb71c73/onnxruntime-1.26.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f2bb870a4b9224eba0a6728c1fa7a9e552b8e59e1083c51fbbc3d013f2b5c0", size = 16052692, upload-time = "2026-05-08T19:07:13.829Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/43/2a4e04f8dbeffad19bbcced4bcd4289bf478921518437404d6b92bdf213b/onnxruntime-1.26.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b6dd70599005bd1bf29779f04a91978b92b5e719c11a20068a8f8e535f725b6", size = 18185439, upload-time = "2026-05-08T19:07:36.299Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/fc/026d0a7162b9c2153dac292baea9e027c42304dc1d9dc6f8ff5b4cfbaedd/onnxruntime-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:a26374dc7fbcaae593601086b242120e13f2310558df0991da6dd8b8fac00414", size = 13026427, upload-time = "2026-05-08T19:08:03.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/27/1dcf88e45e4c69db5f7b106f2dacc3801ba98994e082ca03e1dfdf7bfe57/onnxruntime-1.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:54a8053410fd31fd66469bd754fcfe8a4df9f7eb44756b4b5479bf50c842d948", size = 12796647, upload-time = "2026-05-08T19:07:52.108Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/a2/c801242685e0ce48a4ca51dfafbb588765e0446397e123be53ba5598f3f5/onnxruntime-1.26.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccce19c5f771b8268902f77d9fed9e88f9499465d6780808faa6611a789d33f0", size = 18016563, upload-time = "2026-05-08T19:07:28.081Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/64/0492c0b1db04e29b2630c87cfa36f9d6872b1ca8614b90c5cad58fac7d76/onnxruntime-1.26.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdbed8cf3b672b66acb032f33a253bc27f42bce6ece48ae3fab4fa483a5e96e0", size = 16052634, upload-time = "2026-05-08T19:07:16.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/26/4d09ddc755a84fc8d5e192991626b0e0680e8f6c5d58f4f1d05c42bc48cf/onnxruntime-1.26.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c07af6fc6d5557835f2b6ee7a96d8b3235d0c57a8e230efdedaee106a8a3cbc6", size = 18185632, upload-time = "2026-05-08T19:07:38.756Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/89/3e52249aa08fa301e217ecba07b5246a8338fa2b401e109326e3fc5be0f9/onnxruntime-1.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:61bec80655efa460591c2bc655392d57d2650ce85533a6b9b3b7a790d7ea7916", size = 13026751, upload-time = "2026-05-08T19:08:06.2Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/b3/c1c8782b14af6797c303de132d6eef26a9fb80dfacd3750ce57911d11c6b/onnxruntime-1.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a6677545ff451e3539a02746d2f207d8c5baa4a0a818886bb9d6a6eb9511ee89", size = 12796807, upload-time = "2026-05-08T19:07:54.879Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/f5/47b0676408abec652c14b84d7173e389837832d850c24f87184277313e8d/onnxruntime-1.26.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e016edc15d3c19f36807e1c6b10be5b27807688c32720f91b5ae480a95215d0", size = 16057265, upload-time = "2026-05-08T19:07:19.603Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/45/33ab6deeef010ca844c877dd618cebc079590bbe52d2a3678e7223b1b908/onnxruntime-1.26.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5fc48a91a046a6a5c9b147f83fb41d65d24d24923373b222cdd248f0f4f4aac", size = 18197590, upload-time = "2026-05-08T19:07:41.422Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/89/17546c1c20f6bfc3ae41c22152378a26edfea918af3129e2139dcd7c99f3/onnxruntime-1.26.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:33a791f31432a3af1a96db5e54818b37aba5e5eefc2e6af5794c10a9118a9993", size = 18019724, upload-time = "2026-05-08T19:07:30.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/24/89457a35f6af29538a76647f2c18c3a28277e6c19234c847e7b4b7c19860/onnxruntime-1.26.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e90c00732c4553618103149d93f688e8c3063017938f8983e21a71d9f3b6d22e", size = 16054821, upload-time = "2026-05-08T19:07:22.348Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/f9/15b2e1815cf570d238e0135529f80d2dce64e8e8818a1489cae83823c5c6/onnxruntime-1.26.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01498e80ba8988428d08c2d51b1338f89e3de2a93e6ffe555f79c68f26a5c06b", size = 18185815, upload-time = "2026-05-08T19:07:44.179Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/65/2e11055faf015e4b07f45b513fa49b391baf2e19d92d77d73ebee13c1004/onnxruntime-1.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:7ead61450d8405167c87dd3a31d8da1d576b490a57dab1aa8b82a7da6825f5aa", size = 13349887, upload-time = "2026-05-08T19:08:08.671Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/e4/0f9d1a5718b1781c610c1e354765a3820597081754277a6a9a2b50705702/onnxruntime-1.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:31d71a53490e46910877d0902b5ad99c69a5955e5c7ea6c82863519410e1ba7c", size = 13140121, upload-time = "2026-05-08T19:07:57.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/42/3b8e635f067d06d9f45bede470b8d539d101a4166c272213158dfd08b6ce/onnxruntime-1.26.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b6d258fb78fdfcf049795bcfaa74dcb90ae7baa277afd21e6fd28b83f2c496", size = 16057240, upload-time = "2026-05-08T19:07:25.163Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/99/f2be40a31b908d96b861ae0ce98582fa376c18a7f816b9d5eb4cd6aa0a4c/onnxruntime-1.26.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4eefd386a45202aefb7a5132b94f32df9d506c9edcc7faf2fc60d65183f4b183", size = 18197382, upload-time = "2026-05-08T19:07:46.965Z" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/35/8e/d36f8880bcf18ec026a55807d02fe4c7357da9f25aebd92f85178000c0dc/openai_whisper-20250625.tar.gz", hash = "sha256:37a91a3921809d9f44748ffc73c0a55c9f366c85a3ef5c2ae0cc09540432eb96", size = 803191, upload-time = "2025-06-26T01:06:13.34Z" }
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
@ -1165,6 +1038,21 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf"
|
||||||
|
version = "7.35.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg"
|
name = "psycopg"
|
||||||
version = "3.3.4"
|
version = "3.3.4"
|
||||||
@ -1552,6 +1440,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "15.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markdown-it-py" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.14"
|
version = "0.15.14"
|
||||||
@ -1588,7 +1489,6 @@ dependencies = [
|
|||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "openai-whisper" },
|
|
||||||
{ name = "psycopg", extra = ["binary"] },
|
{ name = "psycopg", extra = ["binary"] },
|
||||||
{ name = "psycopg-pool" },
|
{ name = "psycopg-pool" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
@ -1605,6 +1505,9 @@ dependencies = [
|
|||||||
api = [
|
api = [
|
||||||
{ name = "anthropic" },
|
{ name = "anthropic" },
|
||||||
]
|
]
|
||||||
|
tower = [
|
||||||
|
{ name = "faster-whisper" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
@ -1620,9 +1523,9 @@ requires-dist = [
|
|||||||
{ name = "click", specifier = ">=8.1.0" },
|
{ name = "click", specifier = ">=8.1.0" },
|
||||||
{ name = "embedding-chunking", git = "https://gitea.plantbasedsoutherner.com/petal-power/embedding-chunking.git?tag=v0.1.0" },
|
{ name = "embedding-chunking", git = "https://gitea.plantbasedsoutherner.com/petal-power/embedding-chunking.git?tag=v0.1.0" },
|
||||||
{ name = "fastapi", specifier = ">=0.115.0" },
|
{ name = "fastapi", specifier = ">=0.115.0" },
|
||||||
|
{ name = "faster-whisper", marker = "extra == 'tower'", specifier = ">=1.0.0" },
|
||||||
{ name = "httpx", specifier = ">=0.28.0" },
|
{ name = "httpx", specifier = ">=0.28.0" },
|
||||||
{ name = "jinja2", specifier = ">=3.1.0" },
|
{ name = "jinja2", specifier = ">=3.1.0" },
|
||||||
{ name = "openai-whisper" },
|
|
||||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2" },
|
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2" },
|
||||||
{ name = "psycopg-pool", specifier = ">=3.2" },
|
{ name = "psycopg-pool", specifier = ">=3.2" },
|
||||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||||
@ -1634,7 +1537,7 @@ requires-dist = [
|
|||||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
|
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
|
||||||
{ name = "yt-dlp", specifier = ">=2024.1.0" },
|
{ name = "yt-dlp", specifier = ">=2024.1.0" },
|
||||||
]
|
]
|
||||||
provides-extras = ["api"]
|
provides-extras = ["api", "tower"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
@ -1652,6 +1555,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" },
|
{ url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
@ -1724,18 +1636,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/93/79/920b8e0a8b20f793e8d64855095cb8febabf6175b8550b6f7a547d813891/starlette-1.1.0-py3-none-any.whl", hash = "sha256:7f0dfd38e428aad5cb6f9f667f0ca1d2d8ca3f3385dccac8305f79ec98458382", size = 72899, upload-time = "2026-05-23T16:55:39.201Z" },
|
{ url = "https://files.pythonhosted.org/packages/93/79/920b8e0a8b20f793e8d64855095cb8febabf6175b8550b6f7a547d813891/starlette-1.1.0-py3-none-any.whl", hash = "sha256:7f0dfd38e428aad5cb6f9f667f0ca1d2d8ca3f3385dccac8305f79ec98458382", size = 72899, upload-time = "2026-05-23T16:55:39.201Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sympy"
|
|
||||||
version = "1.14.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "mpmath" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiktoken"
|
name = "tiktoken"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -1792,6 +1692,33 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743, upload-time = "2026-03-06T23:50:32.465Z" },
|
{ url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743, upload-time = "2026-03-06T23:50:32.465Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokenizers"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "huggingface-hub" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@ -1837,50 +1764,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "torch"
|
|
||||||
version = "2.12.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "cuda-bindings", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "cuda-toolkit", extra = ["cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "filelock" },
|
|
||||||
{ name = "fsspec" },
|
|
||||||
{ name = "jinja2" },
|
|
||||||
{ name = "networkx" },
|
|
||||||
{ name = "nvidia-cublas", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "setuptools" },
|
|
||||||
{ name = "sympy" },
|
|
||||||
{ name = "triton", marker = "sys_platform == 'linux'" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ef/bb/285d643f254731294c9b595a007eac39db4600a98682d7bca688f42ca164/torch-2.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b41339df93d491435e790ff8bcbae1c0ce777175889bfd1281d119862793e6a2", size = 88010197, upload-time = "2026-05-13T14:55:35.414Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/81/76debf1db1343bd929bbb5d74c89fb437c2ed88eb144712557e7bd3eea45/torch-2.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8fbef9f108a863e7722a73740998967e3b074742a834fc5be3a535a2befa7057", size = 426376751, upload-time = "2026-05-13T14:55:03.353Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/de/f0/80026028b603c4650ff270fc3785bdef4bd6738765a9cc5a0f5a637d65a2/torch-2.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4b4f64c2c2b11f7510d93dd6412b87025ff6eddd6bb61c3b5a3d892ea20c4756", size = 532261691, upload-time = "2026-05-13T14:52:54.453Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/c2/64b06cbb7830fb3cd9be13e1158b31a3f36b68e6a209105ee3c9d9480be0/torch-2.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:8b958caff4a14d3a3b0b2dfc6a378f64dda9728a9dad28c08a0db9ce4dafb549", size = 122988114, upload-time = "2026-05-13T14:54:42.153Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/ca/01896c80ba921676aa45886b2c5b8d774912de2a1f719de48169c6f755cd/torch-2.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:90dd587a5f61bfe1307148b581e2084fc5bc4a06e2b90a20e9a36b81087ff16b", size = 88009511, upload-time = "2026-05-13T14:54:47.411Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/04/52bdaf4787eab6ac7d7f5851dff934e4def0bc8ead9c8fd2b69b3e529699/torch-2.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:864392c73b7654f4d2b3ae712f607937d0dbb1101c4555fbb41848106b297f39", size = 426383231, upload-time = "2026-05-13T14:53:32.129Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/8a/94bdecd13f5aaa90d45920b89789d9fe7c6f4af8c3cdd7ce01fcb59908fc/torch-2.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5d6b560dfa7d56291c07d615c3bb73e8d9943d9b6d87f76cd0d9d570c4797fa6", size = 532269288, upload-time = "2026-05-13T14:53:49.423Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/2f/bdbaaa267de519ef1b73054bf590d8c93c37a266c9a4e24a01bd38b6918f/torch-2.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:3fee918902090ade827643e758e98363278815de583c75d111fdd665ebffde9f", size = 122987706, upload-time = "2026-05-13T14:54:00.335Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/ad/e95e822f3538171e22640a7fbe839a1fdb666600bf6487025de2ff03b11a/torch-2.12.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:10ee1448a9f304d3b987eb4656f664ba6e4d7b410ca7a5a7c642199777a2cf88", size = 88319556, upload-time = "2026-05-13T14:54:05.574Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/07/055d06d985b445d67422d25b033c11cf55bbb81785d4c4e68e28bca5820e/torch-2.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:af68dbf403439cae9ceaeaaf92f8352b460787dcd27b92aa05c40dd4a19c0f1e", size = 426397656, upload-time = "2026-05-13T14:52:38.84Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/94/b0b4fdc3014122e0a7302fb90086d352aa48f2576f0b252561ebb38c01a8/torch-2.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a6a2eebb237d3b1d9ad3b378e86d9b9e0782afdea8b1e0eba6a13646b9b49c07", size = 532183124, upload-time = "2026-05-13T14:53:16.178Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/c8/052405e6ad05d3237bfe5a4df78f917773956f8e17813a2d44c059068b74/torch-2.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2140e373e9a51a3e22ef62e8d14366d0b470d18f0adf19fdc757368077133a34", size = 123232462, upload-time = "2026-05-13T14:52:27.26Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/dc/ac069f8d6e8be701535921141055293b0d4819d3d7f224a4612cf157c7f9/torch-2.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7dfae4a519197dfa050e98d8e36378a0fb5899625a875c2b54445005a2e404e", size = 88027282, upload-time = "2026-05-13T14:53:05.258Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/c3/1c1eb00e34555b536dddf792676026a988d710ed36981aa00499b36b0620/torch-2.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:891c769072637c74e9a5a77a3bc782894696d8ffec83b938df8536dee7f0ba78", size = 426386961, upload-time = "2026-05-13T14:51:28.406Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/d4/7e730dba0c7032a4154dc9056b76cf9625515e030e269cfbf8098fcfee7d/torch-2.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e2ad3eb85d39c3cab62dfa93ed5a73516e6a53c6713cb97d004004fe089f0f1f", size = 532272265, upload-time = "2026-05-13T14:51:59.308Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/b4/92c80d1bbfee1c0036c06d1d2155a3065bd2423134c83bf8a47e65cd6b9b/torch-2.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:c66696857e987efb8bc1777a37357ec4f60ab5e8af6250b83d6034437fa2d8f3", size = 122987138, upload-time = "2026-05-13T14:51:45.942Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/78/2e12b37ce50a19a037d7bc62d652a5a8f27385a7b05859d6bc9204f20cfe/torch-2.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b4556715c8572758625d62b6e0ae3b1f76c440221913a6fb5e100f321fb4fb02", size = 88320100, upload-time = "2026-05-13T14:51:39.955Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/5e/83c450ec7b0bb40a7b74611c1b5440f9260e33c54c90d556fd4a1f0fd955/torch-2.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a43ac605a5e13116c72b64c359644cce0229f213dde48d2ae0ae5eb5becf7feb", size = 426391871, upload-time = "2026-05-13T14:52:14.989Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/e9/1a0b575d98d0afedd8f157d23fa3d2759421483660448e60d0a4b10b6daa/torch-2.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a7512adfdd7f6732e40de1c620831e3c75b39b98cef60b11d0c5f0a76473ec5", size = 532192241, upload-time = "2026-05-13T14:51:07.795Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/21/afadd25ecd81b3cea1e11c73cf1ab41a983a50271548c3ec7ec3b9efc3e9/torch-2.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f96b63f8287f66a005dd1b5a6abba2920f11156c5e5c4d815f3e2050fd1aa16", size = 123231092, upload-time = "2026-05-13T14:51:18.854Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tqdm"
|
name = "tqdm"
|
||||||
version = "4.67.3"
|
version = "4.67.3"
|
||||||
@ -1912,20 +1795,18 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "triton"
|
name = "typer"
|
||||||
version = "3.7.0"
|
version = "0.25.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-doc" },
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "shellingham" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f7/13/ec05adfcd87311d532ba61e3af143e8be59fcd26675884c4682841406a20/triton-3.7.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bf49b00a7a377a68a6da603a876e797614e6455a80e9021669c476a953ad9a", size = 188505104, upload-time = "2026-05-07T19:05:09.843Z" },
|
{ url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/7b/468a576e35beef1426e0828e28e9ba9e65f5474d496f16ee126c15646324/triton-3.7.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f111161d49bf903c0eaedde3962353a3d841c08a836839b7cc1025b8426efcf", size = 201457567, upload-time = "2026-05-07T18:46:13.505Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/e1/a59a583de59b8f62c495d67c80ee3ea97d09e91ac80c4c6e76456ed8d8ac/triton-3.7.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abdf6beaa89b1bcfb9a43cd990536ce66091a997841a4814b260b7bee4c88c3c", size = 188503209, upload-time = "2026-05-07T19:05:17.935Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/b1/b7507bb9815d403927c8dd51d4158ed2e11751a92dbc118a044f247b6848/triton-3.7.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a35d7afe3f3f058e7ec49fcce09794049e0ffc5c59019ac25ec3413741b8c4e7", size = 201453566, upload-time = "2026-05-07T18:46:20.427Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/8f/0bea7a6a0c989315c9135a1d7fb37e41905cfb3a17cbc1f10044ebd4cc3a/triton-3.7.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc1d61c172d257db80ddf42595131fb196ad2e9bdd751e90fe2ef13531734e8b", size = 188612899, upload-time = "2026-05-07T19:05:24.955Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/02/d96f57828d0912aec733b9bc7e0e7dbfd2c6f079a8fa433ac25cb93d1a30/triton-3.7.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70fb9bbdc9f400afc54bbf6eb2670af28829a6ae3996863317964783141daf56", size = 201553816, upload-time = "2026-05-07T18:46:27.49Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/fb/82a802dac4689f2a2fb2e69302e6a138eecc3e175bbe976ba3cfc717683a/triton-3.7.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a44a8476d0d3571eac4e4d1048e1ff75aad81a09ff4602ccfc56c6dea1672e", size = 188507879, upload-time = "2026-05-07T19:05:32.209Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8f/af/9904ec6d3c93d9b24e5ec360445bbdf758b7f00bfbeedb89cb0eb64eb8bb/triton-3.7.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9b85e72968a9d8bba5ddb24e9b64aaabaf48affb042f2755cb7cfa92b7531ce", size = 201460637, upload-time = "2026-05-07T18:46:34.749Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/f9/4835a8ea746b88727d8899f4e3ccce4f9cacb38abfc3bb0a638266c53111/triton-3.7.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a160de426fd99f92b0baf509045360afbd3bfaa0b4a5171dde800ec9f09684", size = 188608706, upload-time = "2026-05-07T19:05:39.218Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/68/fa86e5a39608000f645535b2c124920126327ab731f8c4fafd5b07ff8d4b/triton-3.7.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce061073102714b725f3660ec6939d94a1da7984b3aa99c921417cae273672f5", size = 201546766, upload-time = "2026-05-07T18:46:42.088Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user