From adecaeee29d096df43e9c628b5de9f6458b268a1 Mon Sep 17 00:00:00 2001 From: Nao Date: Sun, 7 Jun 2026 19:03:46 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Apple=20Silicon=E7=92=B0=E5=A2=83?= =?UTF-8?q?=E3=81=A7=E3=81=AEGUI=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=83=95=E3=83=AA=E3=83=BC=E3=82=BA=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 変更内容: mouth_track_gui/app.py - after(0, ...) をスレッドから呼ぶ問題(macOS Tkinter deadlock)を修正 - UIタスクキュー(queue.Queue)を導入し50ms間隔でポーリングする安全な更新機構に変更 - _poll_ui_queue() / _insert_log() を追加 - log(), _show_error(), _progress_*(), _set_running() 等を ui_queue 経由に統一 mouth_sprite_extractor_gui.py - 解析完了通知を after(0,...) から _poll_analyze_done() ポーリング方式に変更 - 解析開始前にcv2.VideoCaptureを解放しAVFoundationデッドロックを回避 motionpngtuber/mouth_sprite_extractor.py - _run_face_detector() をsubprocess.run からPopen に変更し 解析進捗をリアルタイムでGUIログに流せるよう改善 Co-authored-by: Gemini --- motionpngtuber/mouth_sprite_extractor.py | 1486 +++++++++++----------- mouth_sprite_extractor_gui.py | 49 +- mouth_track_gui/app.py | 68 +- pyproject.toml | 150 +-- uv.lock | 904 +------------ 5 files changed, 899 insertions(+), 1758 deletions(-) diff --git a/motionpngtuber/mouth_sprite_extractor.py b/motionpngtuber/mouth_sprite_extractor.py index 0359178..f55a7dc 100644 --- a/motionpngtuber/mouth_sprite_extractor.py +++ b/motionpngtuber/mouth_sprite_extractor.py @@ -1,108 +1,108 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -mouth_sprite_extractor.py - -動画から口スプライト(5種類のPNG)を自動抽出するコアモジュール。 - -機能: -1. 動画の全フレームから口quadを検出 -2. 口の位置が安定しているフレーム群(クラスタ)を特定 -3. 5種類の口形状を自動選別(open, closed, half, e, u) -4. 楕円マスク+フェザーで透過PNG出力 - -使い方(コマンドライン): - python mouth_sprite_extractor.py --video loop.mp4 --out mouth/ - -使い方(モジュールとして): - from mouth_sprite_extractor import MouthSpriteExtractor - extractor = MouthSpriteExtractor(video_path) - extractor.analyze() - extractor.extract_sprites(output_dir, feather_px=15) -""" - -from __future__ import annotations - -import argparse -import os -import subprocess -import sys -import tempfile -from dataclasses import dataclass, field -from typing import Dict, List, Optional, Tuple - -import cv2 -import numpy as np - -from motionpngtuber.image_io import write_image_file -from motionpngtuber.python_exec import resolve_python_subprocess_executable - - -# --------------------------------------------------------------------------- -# Data classes -# --------------------------------------------------------------------------- - -@dataclass -class MouthFrameInfo: - """1フレームの口情報""" - # 既存フィールド - frame_idx: int - quad: np.ndarray # (4, 2) float32 - center: np.ndarray # (2,) float32 - 口の中心座標 - width: float # 口quadの幅 - height: float # 口quadの高さ - confidence: float # 検出信頼度 - valid: bool # 検出が有効か - - # 新規フィールド(全てデフォルト値あり - 後方互換性のため) - inner_darkness: float = 0.0 # 口内部の暗さ (0.0-1.0) - opening_ratio: float = 0.0 # 開口度 (0.0-1.0) - horizontal_stretch: float = 0.0 # 横への伸び (0.0-1.0) - vertical_compression: float = 0.0 # 縦の圧縮 (0.0-1.0) - lip_curvature: float = 0.0 # 唇の曲率 (-1.0〜1.0) - score_open: float = 0.0 # openタイプのスコア - score_closed: float = 0.0 # closedタイプのスコア - score_half: float = 0.0 # halfタイプのスコア - score_e: float = 0.0 # eタイプのスコア - score_u: float = 0.0 # uタイプのスコア - - -@dataclass -class MouthTypeSelection: - """5種類の口の選択結果""" - open_idx: int # 口を大きく開けたフレーム - closed_idx: int # 口を閉じたフレーム - half_idx: int # 半開きフレーム - e_idx: int # 横長の口フレーム - u_idx: int # すぼめた口フレーム - - def as_dict(self) -> Dict[str, int]: - return { - "open": self.open_idx, - "closed": self.closed_idx, - "half": self.half_idx, - "e": self.e_idx, - "u": self.u_idx, - } - - -# --------------------------------------------------------------------------- -# Geometry helpers -# --------------------------------------------------------------------------- - -def quad_center(quad: np.ndarray) -> np.ndarray: - """quadの中心座標を計算""" - return quad.mean(axis=0).astype(np.float32) - - -def quad_wh(quad: np.ndarray) -> Tuple[float, float]: - """quadの幅と高さを計算""" - quad = np.asarray(quad, dtype=np.float32).reshape(4, 2) - w = float(np.linalg.norm(quad[1] - quad[0])) - h = float(np.linalg.norm(quad[3] - quad[0])) - return w, h - - +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +mouth_sprite_extractor.py + +動画から口スプライト(5種類のPNG)を自動抽出するコアモジュール。 + +機能: +1. 動画の全フレームから口quadを検出 +2. 口の位置が安定しているフレーム群(クラスタ)を特定 +3. 5種類の口形状を自動選別(open, closed, half, e, u) +4. 楕円マスク+フェザーで透過PNG出力 + +使い方(コマンドライン): + python mouth_sprite_extractor.py --video loop.mp4 --out mouth/ + +使い方(モジュールとして): + from mouth_sprite_extractor import MouthSpriteExtractor + extractor = MouthSpriteExtractor(video_path) + extractor.analyze() + extractor.extract_sprites(output_dir, feather_px=15) +""" + +from __future__ import annotations + +import argparse +import os +import subprocess +import sys +import tempfile +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Tuple + +import cv2 +import numpy as np + +from motionpngtuber.image_io import write_image_file +from motionpngtuber.python_exec import resolve_python_subprocess_executable + + +# --------------------------------------------------------------------------- +# Data classes +# --------------------------------------------------------------------------- + +@dataclass +class MouthFrameInfo: + """1フレームの口情報""" + # 既存フィールド + frame_idx: int + quad: np.ndarray # (4, 2) float32 + center: np.ndarray # (2,) float32 - 口の中心座標 + width: float # 口quadの幅 + height: float # 口quadの高さ + confidence: float # 検出信頼度 + valid: bool # 検出が有効か + + # 新規フィールド(全てデフォルト値あり - 後方互換性のため) + inner_darkness: float = 0.0 # 口内部の暗さ (0.0-1.0) + opening_ratio: float = 0.0 # 開口度 (0.0-1.0) + horizontal_stretch: float = 0.0 # 横への伸び (0.0-1.0) + vertical_compression: float = 0.0 # 縦の圧縮 (0.0-1.0) + lip_curvature: float = 0.0 # 唇の曲率 (-1.0〜1.0) + score_open: float = 0.0 # openタイプのスコア + score_closed: float = 0.0 # closedタイプのスコア + score_half: float = 0.0 # halfタイプのスコア + score_e: float = 0.0 # eタイプのスコア + score_u: float = 0.0 # uタイプのスコア + + +@dataclass +class MouthTypeSelection: + """5種類の口の選択結果""" + open_idx: int # 口を大きく開けたフレーム + closed_idx: int # 口を閉じたフレーム + half_idx: int # 半開きフレーム + e_idx: int # 横長の口フレーム + u_idx: int # すぼめた口フレーム + + def as_dict(self) -> Dict[str, int]: + return { + "open": self.open_idx, + "closed": self.closed_idx, + "half": self.half_idx, + "e": self.e_idx, + "u": self.u_idx, + } + + +# --------------------------------------------------------------------------- +# Geometry helpers +# --------------------------------------------------------------------------- + +def quad_center(quad: np.ndarray) -> np.ndarray: + """quadの中心座標を計算""" + return quad.mean(axis=0).astype(np.float32) + + +def quad_wh(quad: np.ndarray) -> Tuple[float, float]: + """quadの幅と高さを計算""" + quad = np.asarray(quad, dtype=np.float32).reshape(4, 2) + w = float(np.linalg.norm(quad[1] - quad[0])) + h = float(np.linalg.norm(quad[3] - quad[0])) + return w, h + + def ensure_even_ge2(n: int) -> int: """偶数に丸める(最小2)""" n = int(n) @@ -129,644 +129,656 @@ def resolve_adjacent_or_repo_script(script_name: str, *, anchor_file: str | None searched = ", ".join(candidates) raise FileNotFoundError(f"Script not found: {script_name} (searched: {searched})") - - -# --------------------------------------------------------------------------- -# Track loading (compatible with face_track_anime_detector.py output) -# --------------------------------------------------------------------------- - -def load_track_data(track_path: str, target_w: int, target_h: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - mouth_track.npz からデータを読み込む。 - - Returns: - quads: (N, 4, 2) float32 - valid: (N,) bool - confidence: (N,) float32 or None - """ - npz = np.load(track_path, allow_pickle=False) - - if "quad" not in npz: - raise ValueError("track must contain 'quad' (N,4,2)") - - quads = np.asarray(npz["quad"], dtype=np.float32) - if quads.ndim != 3 or quads.shape[1:] != (4, 2): - raise ValueError("quad must be (N,4,2)") - - N = int(quads.shape[0]) - - # valid配列 - if "valid" in npz: - valid = np.asarray(npz["valid"], dtype=np.uint8).astype(bool) - if valid.shape[0] != N: - valid = np.ones((N,), bool) - else: - valid = np.ones((N,), bool) - - # スケーリング - src_w = int(npz["w"]) if "w" in npz else target_w - src_h = int(npz["h"]) if "h" in npz else target_h - sx = float(target_w) / float(max(1, src_w)) - sy = float(target_h) / float(max(1, src_h)) - quads = quads.copy() - quads[..., 0] *= sx - quads[..., 1] *= sy - - # confidence配列 - if "confidence" in npz: - confidence = np.asarray(npz["confidence"], dtype=np.float32) - if confidence.shape[0] != N: - confidence = np.ones((N,), dtype=np.float32) - else: - confidence = np.ones((N,), dtype=np.float32) - - return quads, valid, confidence - - -# --------------------------------------------------------------------------- -# Position-aware clustering -# --------------------------------------------------------------------------- - -def find_stable_position_cluster( - centers: np.ndarray, - valid: np.ndarray, - distance_threshold: float = 50.0, -) -> np.ndarray: - """ - 口の中心座標が安定しているフレーム群を特定する。 - - 最も多くのフレームが集まっている位置クラスタを見つけ、 - そのクラスタに属するフレームのマスクを返す。 - - Args: - centers: (N, 2) 口の中心座標 - valid: (N,) 有効フレームマスク - distance_threshold: クラスタ判定の距離閾値(ピクセル) - - Returns: - cluster_mask: (N,) bool - クラスタに属するフレーム - """ - N = len(centers) - valid_indices = np.where(valid)[0] - - if len(valid_indices) < 5: - # フレームが少なすぎる場合はすべて使用 - return valid.copy() - - valid_centers = centers[valid_indices] - - # 各有効フレームについて、近くにいくつのフレームがあるかカウント - counts = np.zeros(len(valid_indices), dtype=np.int32) - for i, c in enumerate(valid_centers): - dists = np.linalg.norm(valid_centers - c, axis=1) - counts[i] = np.sum(dists <= distance_threshold) - - # 最も密なフレームを基準にクラスタを構成 - best_idx = np.argmax(counts) - best_center = valid_centers[best_idx] - - # 基準点からの距離でクラスタを決定 - dists_from_best = np.linalg.norm(valid_centers - best_center, axis=1) - cluster_valid_mask = dists_from_best <= distance_threshold - - # 元のインデックスに変換 - cluster_mask = np.zeros(N, dtype=bool) - for i, orig_idx in enumerate(valid_indices): - if cluster_valid_mask[i]: - cluster_mask[orig_idx] = True - - return cluster_mask - - -# --------------------------------------------------------------------------- -# Mouth type selection -# --------------------------------------------------------------------------- - -def select_5_mouth_types( - mouth_frames: List[MouthFrameInfo], - cluster_mask: np.ndarray, -) -> MouthTypeSelection: - """ - 5種類の口タイプを自動選別する。 - - 選別基準: - - open: 口の高さが最大 - - closed: 口の高さが最小 - - half: 高さが中央値付近 - - e: 幅/高さ比が最大(横長) - - u: 幅が最小かつ高さが中程度(すぼめた口) - - Args: - mouth_frames: 全フレームの口情報 - cluster_mask: 位置クラスタに属するフレームのマスク - - Returns: - MouthTypeSelection - """ - # クラスタ内の有効フレームのみを対象 - candidates = [ - mf for mf in mouth_frames - if mf.valid and cluster_mask[mf.frame_idx] - ] - - if len(candidates) < 5: - # フォールバック: 全有効フレームを使用 - candidates = [mf for mf in mouth_frames if mf.valid] - - if len(candidates) == 0: - raise ValueError("No valid mouth frames found") - - # 各種メトリクス - heights = np.array([mf.height for mf in candidates]) - widths = np.array([mf.width for mf in candidates]) - aspect_ratios = widths / np.maximum(heights, 1e-6) - - used_indices = set() - - def pick_best(scores: np.ndarray, maximize: bool = True) -> int: - """最良のフレームを選択(既に使用済みは除外)""" - sorted_indices = np.argsort(scores) - if maximize: - sorted_indices = sorted_indices[::-1] - - for idx in sorted_indices: - frame_idx = candidates[idx].frame_idx - if frame_idx not in used_indices: - used_indices.add(frame_idx) - return frame_idx - - # フォールバック(すべて使用済みの場合) - return candidates[sorted_indices[0]].frame_idx - - # 1. open: 高さ最大 - open_idx = pick_best(heights, maximize=True) - - # 2. closed: 高さ最小 - closed_idx = pick_best(heights, maximize=False) - - # 3. half: 高さが中央値付近 - median_height = np.median(heights) - half_scores = -np.abs(heights - median_height) # 中央に近いほど高スコア - half_idx = pick_best(half_scores, maximize=True) - - # 4. e: 横長(幅/高さ比が大きい) - e_idx = pick_best(aspect_ratios, maximize=True) - - # 5. u: すぼめた口(幅が小さく、高さは中程度) - # 幅が小さいものを優先、高さは極端でないもの - height_median_dist = np.abs(heights - median_height) - u_scores = -widths - 0.5 * height_median_dist # 幅小さく、高さは中央寄り - u_idx = pick_best(u_scores, maximize=True) - - return MouthTypeSelection( - open_idx=open_idx, - closed_idx=closed_idx, - half_idx=half_idx, - e_idx=e_idx, - u_idx=u_idx, - ) - - -# --------------------------------------------------------------------------- -# Mask generation -# --------------------------------------------------------------------------- - -def make_ellipse_mask(w: int, h: int, rx: int, ry: int) -> np.ndarray: - """楕円マスクを生成(0/255)""" - mask = np.zeros((h, w), dtype=np.uint8) - cx, cy = w // 2, h // 2 - rx = int(max(1, min(rx, w // 2 - 1))) - ry = int(max(1, min(ry, h // 2 - 1))) - cv2.ellipse(mask, (cx, cy), (rx, ry), 0.0, 0.0, 360.0, 255, -1) - return mask - - -def feather_mask(mask_u8: np.ndarray, feather_px: int) -> np.ndarray: - """マスクにフェザー(グラデーション)を適用""" - if feather_px <= 0: - return (mask_u8.astype(np.float32) / 255.0).clip(0.0, 1.0) - - k = 2 * int(feather_px) + 1 - m = cv2.GaussianBlur(mask_u8, (k, k), sigmaX=0) - return (m.astype(np.float32) / 255.0).clip(0.0, 1.0) - - -# --------------------------------------------------------------------------- -# Sprite extraction -# --------------------------------------------------------------------------- - -def warp_frame_to_norm( - frame_bgr: np.ndarray, - quad: np.ndarray, - norm_w: int, - norm_h: int, -) -> np.ndarray: - """フレームから口パッチを正規化空間に変換""" - src = np.asarray(quad, dtype=np.float32).reshape(4, 2) - dst = np.array([ - [0, 0], - [norm_w - 1, 0], - [norm_w - 1, norm_h - 1], - [0, norm_h - 1], - ], dtype=np.float32) - - M = cv2.getPerspectiveTransform(src, dst) - patch = cv2.warpPerspective( - frame_bgr, - M, - (int(norm_w), int(norm_h)), - flags=cv2.INTER_LINEAR, - borderMode=cv2.BORDER_REPLICATE, - ) - return patch - - -def extract_mouth_sprite( - frame_bgr: np.ndarray, - quad: np.ndarray, - unified_w: int, - unified_h: int, - feather_px: int = 15, - mask_scale: float = 0.85, -) -> np.ndarray: - """ - フレームから口スプライトを抽出する。 - - Args: - frame_bgr: 入力フレーム(BGR) - quad: 口のquad (4, 2) - unified_w: 出力幅 - unified_h: 出力高さ - feather_px: フェザー幅(ピクセル) - mask_scale: マスクの楕円サイズ(0.0-1.0) - - Returns: - rgba: (H, W, 4) uint8 - 透過PNG用 - """ - # 正規化空間に変換 - patch_bgr = warp_frame_to_norm(frame_bgr, quad, unified_w, unified_h) - patch_rgb = cv2.cvtColor(patch_bgr, cv2.COLOR_BGR2RGB) - - # 楕円マスク生成 - rx = int((unified_w * mask_scale) * 0.5) - ry = int((unified_h * mask_scale) * 0.5) - mask_u8 = make_ellipse_mask(unified_w, unified_h, rx, ry) - - # フェザー適用 - mask_f = feather_mask(mask_u8, feather_px) - - # RGBA画像を生成 - rgba = np.zeros((unified_h, unified_w, 4), dtype=np.uint8) - rgba[:, :, :3] = patch_rgb - rgba[:, :, 3] = (mask_f * 255).astype(np.uint8) - - return rgba - - -def compute_unified_size( - mouth_frames: List[MouthFrameInfo], - selected_indices: List[int], - padding: float = 1.1, -) -> Tuple[int, int]: - """ - 選択されたフレームの口がすべて収まるサイズを計算。 - - Args: - mouth_frames: 全フレームの口情報 - selected_indices: 選択されたフレームインデックス - padding: 余白係数 - - Returns: - (width, height) - """ - idx_to_mf = {mf.frame_idx: mf for mf in mouth_frames} - - max_w = 0.0 - max_h = 0.0 - for idx in selected_indices: - if idx in idx_to_mf: - mf = idx_to_mf[idx] - max_w = max(max_w, mf.width) - max_h = max(max_h, mf.height) - - # パディングを適用して偶数に丸める - w = ensure_even_ge2(int(max_w * padding)) - h = ensure_even_ge2(int(max_h * padding)) - - return w, h - - -# --------------------------------------------------------------------------- -# Main extractor class -# --------------------------------------------------------------------------- - -class MouthSpriteExtractor: - """口スプライト抽出器""" - - def __init__(self, video_path: str, track_path: str = ""): - """ - Args: - video_path: 入力動画のパス - track_path: mouth_track.npz のパス(空の場合は自動検索) - """ - self.video_path = video_path - self.track_path = track_path - - # 動画情報 - self.vid_w = 0 - self.vid_h = 0 - self.fps = 0.0 - self.n_frames = 0 - - # 解析結果 - self.mouth_frames: List[MouthFrameInfo] = [] - self.cluster_mask: Optional[np.ndarray] = None - self.selection: Optional[MouthTypeSelection] = None - self.unified_size: Optional[Tuple[int, int]] = None - - self._load_video_info() - - def _load_video_info(self): - """動画の情報を取得""" - cap = cv2.VideoCapture(self.video_path) - if not cap.isOpened(): - raise RuntimeError(f"Failed to open video: {self.video_path}") - - self.vid_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - self.vid_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - self.fps = float(cap.get(cv2.CAP_PROP_FPS) or 30.0) - self.n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0) - cap.release() - - def _find_track_path(self) -> str: - """トラックファイルを自動検索""" - if self.track_path and os.path.isfile(self.track_path): - return self.track_path - - video_dir = os.path.dirname(os.path.abspath(self.video_path)) - candidates = [ - os.path.join(video_dir, "mouth_track_calibrated.npz"), - os.path.join(video_dir, "mouth_track.npz"), - ] - - for cand in candidates: - if os.path.isfile(cand): - return cand - - return "" - + + +# --------------------------------------------------------------------------- +# Track loading (compatible with face_track_anime_detector.py output) +# --------------------------------------------------------------------------- + +def load_track_data(track_path: str, target_w: int, target_h: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + mouth_track.npz からデータを読み込む。 + + Returns: + quads: (N, 4, 2) float32 + valid: (N,) bool + confidence: (N,) float32 or None + """ + npz = np.load(track_path, allow_pickle=False) + + if "quad" not in npz: + raise ValueError("track must contain 'quad' (N,4,2)") + + quads = np.asarray(npz["quad"], dtype=np.float32) + if quads.ndim != 3 or quads.shape[1:] != (4, 2): + raise ValueError("quad must be (N,4,2)") + + N = int(quads.shape[0]) + + # valid配列 + if "valid" in npz: + valid = np.asarray(npz["valid"], dtype=np.uint8).astype(bool) + if valid.shape[0] != N: + valid = np.ones((N,), bool) + else: + valid = np.ones((N,), bool) + + # スケーリング + src_w = int(npz["w"]) if "w" in npz else target_w + src_h = int(npz["h"]) if "h" in npz else target_h + sx = float(target_w) / float(max(1, src_w)) + sy = float(target_h) / float(max(1, src_h)) + quads = quads.copy() + quads[..., 0] *= sx + quads[..., 1] *= sy + + # confidence配列 + if "confidence" in npz: + confidence = np.asarray(npz["confidence"], dtype=np.float32) + if confidence.shape[0] != N: + confidence = np.ones((N,), dtype=np.float32) + else: + confidence = np.ones((N,), dtype=np.float32) + + return quads, valid, confidence + + +# --------------------------------------------------------------------------- +# Position-aware clustering +# --------------------------------------------------------------------------- + +def find_stable_position_cluster( + centers: np.ndarray, + valid: np.ndarray, + distance_threshold: float = 50.0, +) -> np.ndarray: + """ + 口の中心座標が安定しているフレーム群を特定する。 + + 最も多くのフレームが集まっている位置クラスタを見つけ、 + そのクラスタに属するフレームのマスクを返す。 + + Args: + centers: (N, 2) 口の中心座標 + valid: (N,) 有効フレームマスク + distance_threshold: クラスタ判定の距離閾値(ピクセル) + + Returns: + cluster_mask: (N,) bool - クラスタに属するフレーム + """ + N = len(centers) + valid_indices = np.where(valid)[0] + + if len(valid_indices) < 5: + # フレームが少なすぎる場合はすべて使用 + return valid.copy() + + valid_centers = centers[valid_indices] + + # 各有効フレームについて、近くにいくつのフレームがあるかカウント + counts = np.zeros(len(valid_indices), dtype=np.int32) + for i, c in enumerate(valid_centers): + dists = np.linalg.norm(valid_centers - c, axis=1) + counts[i] = np.sum(dists <= distance_threshold) + + # 最も密なフレームを基準にクラスタを構成 + best_idx = np.argmax(counts) + best_center = valid_centers[best_idx] + + # 基準点からの距離でクラスタを決定 + dists_from_best = np.linalg.norm(valid_centers - best_center, axis=1) + cluster_valid_mask = dists_from_best <= distance_threshold + + # 元のインデックスに変換 + cluster_mask = np.zeros(N, dtype=bool) + for i, orig_idx in enumerate(valid_indices): + if cluster_valid_mask[i]: + cluster_mask[orig_idx] = True + + return cluster_mask + + +# --------------------------------------------------------------------------- +# Mouth type selection +# --------------------------------------------------------------------------- + +def select_5_mouth_types( + mouth_frames: List[MouthFrameInfo], + cluster_mask: np.ndarray, +) -> MouthTypeSelection: + """ + 5種類の口タイプを自動選別する。 + + 選別基準: + - open: 口の高さが最大 + - closed: 口の高さが最小 + - half: 高さが中央値付近 + - e: 幅/高さ比が最大(横長) + - u: 幅が最小かつ高さが中程度(すぼめた口) + + Args: + mouth_frames: 全フレームの口情報 + cluster_mask: 位置クラスタに属するフレームのマスク + + Returns: + MouthTypeSelection + """ + # クラスタ内の有効フレームのみを対象 + candidates = [ + mf for mf in mouth_frames + if mf.valid and cluster_mask[mf.frame_idx] + ] + + if len(candidates) < 5: + # フォールバック: 全有効フレームを使用 + candidates = [mf for mf in mouth_frames if mf.valid] + + if len(candidates) == 0: + raise ValueError("No valid mouth frames found") + + # 各種メトリクス + heights = np.array([mf.height for mf in candidates]) + widths = np.array([mf.width for mf in candidates]) + aspect_ratios = widths / np.maximum(heights, 1e-6) + + used_indices = set() + + def pick_best(scores: np.ndarray, maximize: bool = True) -> int: + """最良のフレームを選択(既に使用済みは除外)""" + sorted_indices = np.argsort(scores) + if maximize: + sorted_indices = sorted_indices[::-1] + + for idx in sorted_indices: + frame_idx = candidates[idx].frame_idx + if frame_idx not in used_indices: + used_indices.add(frame_idx) + return frame_idx + + # フォールバック(すべて使用済みの場合) + return candidates[sorted_indices[0]].frame_idx + + # 1. open: 高さ最大 + open_idx = pick_best(heights, maximize=True) + + # 2. closed: 高さ最小 + closed_idx = pick_best(heights, maximize=False) + + # 3. half: 高さが中央値付近 + median_height = np.median(heights) + half_scores = -np.abs(heights - median_height) # 中央に近いほど高スコア + half_idx = pick_best(half_scores, maximize=True) + + # 4. e: 横長(幅/高さ比が大きい) + e_idx = pick_best(aspect_ratios, maximize=True) + + # 5. u: すぼめた口(幅が小さく、高さは中程度) + # 幅が小さいものを優先、高さは極端でないもの + height_median_dist = np.abs(heights - median_height) + u_scores = -widths - 0.5 * height_median_dist # 幅小さく、高さは中央寄り + u_idx = pick_best(u_scores, maximize=True) + + return MouthTypeSelection( + open_idx=open_idx, + closed_idx=closed_idx, + half_idx=half_idx, + e_idx=e_idx, + u_idx=u_idx, + ) + + +# --------------------------------------------------------------------------- +# Mask generation +# --------------------------------------------------------------------------- + +def make_ellipse_mask(w: int, h: int, rx: int, ry: int) -> np.ndarray: + """楕円マスクを生成(0/255)""" + mask = np.zeros((h, w), dtype=np.uint8) + cx, cy = w // 2, h // 2 + rx = int(max(1, min(rx, w // 2 - 1))) + ry = int(max(1, min(ry, h // 2 - 1))) + cv2.ellipse(mask, (cx, cy), (rx, ry), 0.0, 0.0, 360.0, 255, -1) + return mask + + +def feather_mask(mask_u8: np.ndarray, feather_px: int) -> np.ndarray: + """マスクにフェザー(グラデーション)を適用""" + if feather_px <= 0: + return (mask_u8.astype(np.float32) / 255.0).clip(0.0, 1.0) + + k = 2 * int(feather_px) + 1 + m = cv2.GaussianBlur(mask_u8, (k, k), sigmaX=0) + return (m.astype(np.float32) / 255.0).clip(0.0, 1.0) + + +# --------------------------------------------------------------------------- +# Sprite extraction +# --------------------------------------------------------------------------- + +def warp_frame_to_norm( + frame_bgr: np.ndarray, + quad: np.ndarray, + norm_w: int, + norm_h: int, +) -> np.ndarray: + """フレームから口パッチを正規化空間に変換""" + src = np.asarray(quad, dtype=np.float32).reshape(4, 2) + dst = np.array([ + [0, 0], + [norm_w - 1, 0], + [norm_w - 1, norm_h - 1], + [0, norm_h - 1], + ], dtype=np.float32) + + M = cv2.getPerspectiveTransform(src, dst) + patch = cv2.warpPerspective( + frame_bgr, + M, + (int(norm_w), int(norm_h)), + flags=cv2.INTER_LINEAR, + borderMode=cv2.BORDER_REPLICATE, + ) + return patch + + +def extract_mouth_sprite( + frame_bgr: np.ndarray, + quad: np.ndarray, + unified_w: int, + unified_h: int, + feather_px: int = 15, + mask_scale: float = 0.85, +) -> np.ndarray: + """ + フレームから口スプライトを抽出する。 + + Args: + frame_bgr: 入力フレーム(BGR) + quad: 口のquad (4, 2) + unified_w: 出力幅 + unified_h: 出力高さ + feather_px: フェザー幅(ピクセル) + mask_scale: マスクの楕円サイズ(0.0-1.0) + + Returns: + rgba: (H, W, 4) uint8 - 透過PNG用 + """ + # 正規化空間に変換 + patch_bgr = warp_frame_to_norm(frame_bgr, quad, unified_w, unified_h) + patch_rgb = cv2.cvtColor(patch_bgr, cv2.COLOR_BGR2RGB) + + # 楕円マスク生成 + rx = int((unified_w * mask_scale) * 0.5) + ry = int((unified_h * mask_scale) * 0.5) + mask_u8 = make_ellipse_mask(unified_w, unified_h, rx, ry) + + # フェザー適用 + mask_f = feather_mask(mask_u8, feather_px) + + # RGBA画像を生成 + rgba = np.zeros((unified_h, unified_w, 4), dtype=np.uint8) + rgba[:, :, :3] = patch_rgb + rgba[:, :, 3] = (mask_f * 255).astype(np.uint8) + + return rgba + + +def compute_unified_size( + mouth_frames: List[MouthFrameInfo], + selected_indices: List[int], + padding: float = 1.1, +) -> Tuple[int, int]: + """ + 選択されたフレームの口がすべて収まるサイズを計算。 + + Args: + mouth_frames: 全フレームの口情報 + selected_indices: 選択されたフレームインデックス + padding: 余白係数 + + Returns: + (width, height) + """ + idx_to_mf = {mf.frame_idx: mf for mf in mouth_frames} + + max_w = 0.0 + max_h = 0.0 + for idx in selected_indices: + if idx in idx_to_mf: + mf = idx_to_mf[idx] + max_w = max(max_w, mf.width) + max_h = max(max_h, mf.height) + + # パディングを適用して偶数に丸める + w = ensure_even_ge2(int(max_w * padding)) + h = ensure_even_ge2(int(max_h * padding)) + + return w, h + + +# --------------------------------------------------------------------------- +# Main extractor class +# --------------------------------------------------------------------------- + +class MouthSpriteExtractor: + """口スプライト抽出器""" + + def __init__(self, video_path: str, track_path: str = ""): + """ + Args: + video_path: 入力動画のパス + track_path: mouth_track.npz のパス(空の場合は自動検索) + """ + self.video_path = video_path + self.track_path = track_path + + # 動画情報 + self.vid_w = 0 + self.vid_h = 0 + self.fps = 0.0 + self.n_frames = 0 + + # 解析結果 + self.mouth_frames: List[MouthFrameInfo] = [] + self.cluster_mask: Optional[np.ndarray] = None + self.selection: Optional[MouthTypeSelection] = None + self.unified_size: Optional[Tuple[int, int]] = None + + self._load_video_info() + + def _load_video_info(self): + """動画の情報を取得""" + cap = cv2.VideoCapture(self.video_path) + if not cap.isOpened(): + raise RuntimeError(f"Failed to open video: {self.video_path}") + + self.vid_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + self.vid_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + self.fps = float(cap.get(cv2.CAP_PROP_FPS) or 30.0) + self.n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0) + cap.release() + + def _find_track_path(self) -> str: + """トラックファイルを自動検索""" + if self.track_path and os.path.isfile(self.track_path): + return self.track_path + + video_dir = os.path.dirname(os.path.abspath(self.video_path)) + candidates = [ + os.path.join(video_dir, "mouth_track_calibrated.npz"), + os.path.join(video_dir, "mouth_track.npz"), + ] + + for cand in candidates: + if os.path.isfile(cand): + return cand + + return "" + def _run_face_detector(self, callback=None) -> str: """face_track_anime_detector.py を実行してトラッキング""" video_dir = os.path.dirname(os.path.abspath(self.video_path)) track_out = os.path.join(video_dir, "mouth_track.npz") - - # 既存ファイルがあれば使用 - if os.path.isfile(track_out): - return track_out - - # face_track_anime_detector.py を実行 + + # 既存ファイルがあれば使用 + if os.path.isfile(track_out): + return track_out + + # face_track_anime_detector.py を実行 detector_script = resolve_adjacent_or_repo_script("face_track_anime_detector.py") cmd = [ resolve_python_subprocess_executable(), detector_script, - "--video", self.video_path, - "--out", track_out, - "--device", "auto", - ] - - if callback: - callback("Running face detector...") - - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - raise RuntimeError(f"Face detector failed: {result.stderr}") - - return track_out - - def analyze(self, callback=None, position_threshold: float = 50.0) -> None: - """ - 動画を解析して口情報を取得。 - - Args: - callback: 進捗コールバック関数(文字列を受け取る) - position_threshold: 位置クラスタリングの閾値(ピクセル) - """ - # トラックファイルを取得 - track_path = self._find_track_path() - if not track_path: - if callback: - callback("Track file not found. Running face detector...") - track_path = self._run_face_detector(callback) - - if callback: - callback(f"Loading track: {os.path.basename(track_path)}") - - # トラックデータ読み込み - quads, valid, confidence = load_track_data( - track_path, self.vid_w, self.vid_h - ) - - # フレーム情報を構築 - self.mouth_frames = [] - for i in range(len(quads)): - quad = quads[i] - center = quad_center(quad) - w, h = quad_wh(quad) - - mf = MouthFrameInfo( - frame_idx=i, - quad=quad, - center=center, - width=w, - height=h, - confidence=float(confidence[i]), - valid=bool(valid[i]), - ) - self.mouth_frames.append(mf) - - if callback: - callback(f"Analyzed {len(self.mouth_frames)} frames") - - # 位置クラスタリング - centers = np.array([mf.center for mf in self.mouth_frames]) - valid_array = np.array([mf.valid for mf in self.mouth_frames]) - - self.cluster_mask = find_stable_position_cluster( - centers, valid_array, distance_threshold=position_threshold - ) - - cluster_count = int(self.cluster_mask.sum()) - if callback: - callback(f"Found stable cluster with {cluster_count} frames") - - # 5種類の口を選別 - self.selection = select_5_mouth_types(self.mouth_frames, self.cluster_mask) - - # 統一サイズを計算 - selected_indices = list(self.selection.as_dict().values()) - self.unified_size = compute_unified_size( - self.mouth_frames, selected_indices - ) - - if callback: - callback(f"Selected frames: {self.selection.as_dict()}") - callback(f"Unified size: {self.unified_size[0]}x{self.unified_size[1]}") - - def get_preview_sprites(self, feather_px: int = 15) -> Dict[str, np.ndarray]: - """ - プレビュー用のスプライトを取得。 - - Args: - feather_px: フェザー幅 - - Returns: - {"open": rgba, "closed": rgba, ...} - """ - if self.selection is None or self.unified_size is None: - raise RuntimeError("Must call analyze() first") - - cap = cv2.VideoCapture(self.video_path) - if not cap.isOpened(): - raise RuntimeError(f"Failed to open video: {self.video_path}") - - unified_w, unified_h = self.unified_size - idx_to_mf = {mf.frame_idx: mf for mf in self.mouth_frames} - - sprites = {} - for name, frame_idx in self.selection.as_dict().items(): - cap.set(cv2.CAP_PROP_POS_FRAMES, float(frame_idx)) - ok, frame = cap.read() - if not ok or frame is None: - continue - - mf = idx_to_mf[frame_idx] - rgba = extract_mouth_sprite( - frame, mf.quad, unified_w, unified_h, feather_px - ) - sprites[name] = rgba - - cap.release() - return sprites - - def extract_sprites( - self, - output_dir: str, - feather_px: int = 15, - callback=None, - ) -> Dict[str, str]: - """ - スプライトを抽出してファイルに保存。 - - Args: - output_dir: 出力ディレクトリ - feather_px: フェザー幅 - callback: 進捗コールバック - - Returns: - {"open": path, "closed": path, ...} - """ - if self.selection is None or self.unified_size is None: - raise RuntimeError("Must call analyze() first") - - # 出力ディレクトリを作成 - os.makedirs(output_dir, exist_ok=True) - - # スプライトを取得 - sprites = self.get_preview_sprites(feather_px) - - # ファイルに保存 - saved_paths = {} - for name, rgba in sprites.items(): - filename = f"{name}.png" - filepath = os.path.join(output_dir, filename) - - # RGBA画像として保存 - ok = write_image_file( - filepath, - cv2.cvtColor(rgba, cv2.COLOR_RGBA2BGRA), - ) - if not ok: - raise RuntimeError(f"Failed to save sprite: {filepath}") - saved_paths[name] = filepath - - if callback: - callback(f"Saved: {filename}") - - return saved_paths - - -# --------------------------------------------------------------------------- -# Utility functions for output directory -# --------------------------------------------------------------------------- - -def get_unique_output_dir(base_dir: str) -> str: - """ - 一意の出力ディレクトリ名を生成。 - - base_dir が存在しなければそのまま返す。 - 存在すれば "_001", "_002" 等のサフィックスを付ける。 - """ - if not os.path.exists(base_dir): - return base_dir - - i = 1 - while True: - new_dir = f"{base_dir}_{i:03d}" - if not os.path.exists(new_dir): - return new_dir - i += 1 - - -# --------------------------------------------------------------------------- -# CLI entry point -# --------------------------------------------------------------------------- - -def main() -> int: - ap = argparse.ArgumentParser( - description="Extract mouth sprites from video" - ) - ap.add_argument("--video", required=True, help="Input video file") - ap.add_argument("--track", default="", help="mouth_track.npz (optional)") - ap.add_argument("--out", default="", help="Output directory (default: mouth/ next to video)") - ap.add_argument("--feather", type=int, default=15, help="Feather width in pixels") - ap.add_argument("--position-threshold", type=float, default=50.0, - help="Position clustering threshold in pixels") - args = ap.parse_args() - - if not os.path.isfile(args.video): - print(f"[error] Video not found: {args.video}") - return 1 - - # 出力ディレクトリ - if args.out: - output_dir = args.out - else: - video_dir = os.path.dirname(os.path.abspath(args.video)) - output_dir = os.path.join(video_dir, "mouth") - - output_dir = get_unique_output_dir(output_dir) - - def log(msg: str): - print(f"[info] {msg}") - - try: - extractor = MouthSpriteExtractor(args.video, args.track) - log(f"Video: {extractor.vid_w}x{extractor.vid_h} @ {extractor.fps:.2f}fps") - - extractor.analyze(callback=log, position_threshold=args.position_threshold) - - saved = extractor.extract_sprites(output_dir, args.feather, callback=log) - - log(f"Output directory: {output_dir}") - log(f"Saved {len(saved)} sprites") - - return 0 - - except Exception as e: - print(f"[error] {e}") - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) + "--video", self.video_path, + "--out", track_out, + "--device", "auto", + ] + + if callback: + callback("Running face detector...") + + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True, + ) + for line in process.stdout: + line = line.strip() + if line and callback: + callback(line) + process.wait() + if process.returncode != 0: + raise RuntimeError(f"Face detector failed with exit code {process.returncode}") + + return track_out + + def analyze(self, callback=None, position_threshold: float = 50.0) -> None: + """ + 動画を解析して口情報を取得。 + + Args: + callback: 進捗コールバック関数(文字列を受け取る) + position_threshold: 位置クラスタリングの閾値(ピクセル) + """ + # トラックファイルを取得 + track_path = self._find_track_path() + if not track_path: + if callback: + callback("Track file not found. Running face detector...") + track_path = self._run_face_detector(callback) + + if callback: + callback(f"Loading track: {os.path.basename(track_path)}") + + # トラックデータ読み込み + quads, valid, confidence = load_track_data( + track_path, self.vid_w, self.vid_h + ) + + # フレーム情報を構築 + self.mouth_frames = [] + for i in range(len(quads)): + quad = quads[i] + center = quad_center(quad) + w, h = quad_wh(quad) + + mf = MouthFrameInfo( + frame_idx=i, + quad=quad, + center=center, + width=w, + height=h, + confidence=float(confidence[i]), + valid=bool(valid[i]), + ) + self.mouth_frames.append(mf) + + if callback: + callback(f"Analyzed {len(self.mouth_frames)} frames") + + # 位置クラスタリング + centers = np.array([mf.center for mf in self.mouth_frames]) + valid_array = np.array([mf.valid for mf in self.mouth_frames]) + + self.cluster_mask = find_stable_position_cluster( + centers, valid_array, distance_threshold=position_threshold + ) + + cluster_count = int(self.cluster_mask.sum()) + if callback: + callback(f"Found stable cluster with {cluster_count} frames") + + # 5種類の口を選別 + self.selection = select_5_mouth_types(self.mouth_frames, self.cluster_mask) + + # 統一サイズを計算 + selected_indices = list(self.selection.as_dict().values()) + self.unified_size = compute_unified_size( + self.mouth_frames, selected_indices + ) + + if callback: + callback(f"Selected frames: {self.selection.as_dict()}") + callback(f"Unified size: {self.unified_size[0]}x{self.unified_size[1]}") + + def get_preview_sprites(self, feather_px: int = 15) -> Dict[str, np.ndarray]: + """ + プレビュー用のスプライトを取得。 + + Args: + feather_px: フェザー幅 + + Returns: + {"open": rgba, "closed": rgba, ...} + """ + if self.selection is None or self.unified_size is None: + raise RuntimeError("Must call analyze() first") + + cap = cv2.VideoCapture(self.video_path) + if not cap.isOpened(): + raise RuntimeError(f"Failed to open video: {self.video_path}") + + unified_w, unified_h = self.unified_size + idx_to_mf = {mf.frame_idx: mf for mf in self.mouth_frames} + + sprites = {} + for name, frame_idx in self.selection.as_dict().items(): + cap.set(cv2.CAP_PROP_POS_FRAMES, float(frame_idx)) + ok, frame = cap.read() + if not ok or frame is None: + continue + + mf = idx_to_mf[frame_idx] + rgba = extract_mouth_sprite( + frame, mf.quad, unified_w, unified_h, feather_px + ) + sprites[name] = rgba + + cap.release() + return sprites + + def extract_sprites( + self, + output_dir: str, + feather_px: int = 15, + callback=None, + ) -> Dict[str, str]: + """ + スプライトを抽出してファイルに保存。 + + Args: + output_dir: 出力ディレクトリ + feather_px: フェザー幅 + callback: 進捗コールバック + + Returns: + {"open": path, "closed": path, ...} + """ + if self.selection is None or self.unified_size is None: + raise RuntimeError("Must call analyze() first") + + # 出力ディレクトリを作成 + os.makedirs(output_dir, exist_ok=True) + + # スプライトを取得 + sprites = self.get_preview_sprites(feather_px) + + # ファイルに保存 + saved_paths = {} + for name, rgba in sprites.items(): + filename = f"{name}.png" + filepath = os.path.join(output_dir, filename) + + # RGBA画像として保存 + ok = write_image_file( + filepath, + cv2.cvtColor(rgba, cv2.COLOR_RGBA2BGRA), + ) + if not ok: + raise RuntimeError(f"Failed to save sprite: {filepath}") + saved_paths[name] = filepath + + if callback: + callback(f"Saved: {filename}") + + return saved_paths + + +# --------------------------------------------------------------------------- +# Utility functions for output directory +# --------------------------------------------------------------------------- + +def get_unique_output_dir(base_dir: str) -> str: + """ + 一意の出力ディレクトリ名を生成。 + + base_dir が存在しなければそのまま返す。 + 存在すれば "_001", "_002" 等のサフィックスを付ける。 + """ + if not os.path.exists(base_dir): + return base_dir + + i = 1 + while True: + new_dir = f"{base_dir}_{i:03d}" + if not os.path.exists(new_dir): + return new_dir + i += 1 + + +# --------------------------------------------------------------------------- +# CLI entry point +# --------------------------------------------------------------------------- + +def main() -> int: + ap = argparse.ArgumentParser( + description="Extract mouth sprites from video" + ) + ap.add_argument("--video", required=True, help="Input video file") + ap.add_argument("--track", default="", help="mouth_track.npz (optional)") + ap.add_argument("--out", default="", help="Output directory (default: mouth/ next to video)") + ap.add_argument("--feather", type=int, default=15, help="Feather width in pixels") + ap.add_argument("--position-threshold", type=float, default=50.0, + help="Position clustering threshold in pixels") + args = ap.parse_args() + + if not os.path.isfile(args.video): + print(f"[error] Video not found: {args.video}") + return 1 + + # 出力ディレクトリ + if args.out: + output_dir = args.out + else: + video_dir = os.path.dirname(os.path.abspath(args.video)) + output_dir = os.path.join(video_dir, "mouth") + + output_dir = get_unique_output_dir(output_dir) + + def log(msg: str): + print(f"[info] {msg}") + + try: + extractor = MouthSpriteExtractor(args.video, args.track) + log(f"Video: {extractor.vid_w}x{extractor.vid_h} @ {extractor.fps:.2f}fps") + + extractor.analyze(callback=log, position_threshold=args.position_threshold) + + saved = extractor.extract_sprites(output_dir, args.feather, callback=log) + + log(f"Output directory: {output_dir}") + log(f"Saved {len(saved)} sprites") + + return 0 + + except Exception as e: + print(f"[error] {e}") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/mouth_sprite_extractor_gui.py b/mouth_sprite_extractor_gui.py index 0b29d21..8a86e1e 100644 --- a/mouth_sprite_extractor_gui.py +++ b/mouth_sprite_extractor_gui.py @@ -1954,6 +1954,7 @@ def _on_analyze(self): return self.is_analyzing = True + self.analyze_error = None self._refresh_workflow_state() self._start_busy_state("解析中", "口位置を解析しています。しばらくお待ちください") self.analyze_btn.configure(state=tk.DISABLED) @@ -1966,17 +1967,51 @@ def _on_analyze(self): self._clear_candidates() self._clear_preview("empty") + # MacOS AVFoundation deadlock workaround: + # cv2.VideoCapture cannot be opened from a background thread if the main thread holds it open + self._close_player_capture() + if self._cached_cap: + self._cached_cap.release() + self._cached_cap = None + thread = threading.Thread(target=self._analyze_worker, daemon=True) thread.start() + self.after(100, self._poll_analyze_done) + + def _poll_analyze_done(self): + if self.is_analyzing: + self.after(100, self._poll_analyze_done) + return + + if self.analyze_error: + self._finish_busy_state("解析エラー") + else: + self._finish_busy_state("解析完了") + self._enable_manual_pick_controls(True) + self.auto_fill_btn.configure(state=tk.NORMAL) + self._show_player_frame(self.player_current_frame_idx) + + self.analyze_btn.configure(state=tk.NORMAL) + self._refresh_workflow_state() def _analyze_worker(self): """解析ワーカースレッド""" + def trace(msg): + with open("/tmp/debug.txt", "a") as f: + f.write(msg + "\n") + try: + import time + time.sleep(0.2) # Wait for AVFoundation to completely release the previous capture asynchronously + trace("1. starting worker") self.log("解析を開始...") + trace("2. creating extractor") self.extractor = MouthSpriteExtractor(self.video_path) + trace("3. running analyze") self.extractor.analyze(callback=self.log) - + + trace("4. analyze done") valid_frames = [mf for mf in self.extractor.mouth_frames if mf.valid] self.valid_frames = valid_frames self._mouth_frame_by_idx = { @@ -1999,20 +2034,18 @@ def _analyze_worker(self): self.log("解析完了。プレイヤーで候補フレームを手動追加してください。") self.log("必要なら『候補を自動選出』で従来の自動抽出も使えます。") - self.after(0, lambda: self._finish_busy_state("解析完了")) - self.after(0, lambda: self._enable_manual_pick_controls(True)) - self.after(0, lambda: self.auto_fill_btn.configure(state=tk.NORMAL)) - self.after(0, lambda: self._show_player_frame(self.player_current_frame_idx)) + trace("5. queuing UI updates") except Exception as e: + trace(f"ERROR: {e}") self.log(f"エラー: {e}") - self.after(0, lambda: self._finish_busy_state("解析エラー")) + self.analyze_error = str(e) traceback.print_exc() finally: + trace("7. finally block") self.is_analyzing = False - self.after(0, lambda: self.analyze_btn.configure(state=tk.NORMAL)) - self.after(0, self._refresh_workflow_state) + trace("8. worker exit") def _get_video_capture(self) -> cv2.VideoCapture: """キャッシュされたVideoCaptureを取得""" diff --git a/mouth_track_gui/app.py b/mouth_track_gui/app.py index a1c5e9b..5531d89 100644 --- a/mouth_track_gui/app.py +++ b/mouth_track_gui/app.py @@ -119,6 +119,10 @@ class App(tk.Tk): def __init__(self) -> None: super().__init__() + import queue + self.ui_queue = queue.Queue() + self._poll_ui_queue() + self.title("Mouth Track One-Click (HQ)") self.geometry("1180x860") @@ -463,14 +467,15 @@ def _cancel_live_color_control_job(self) -> None: self._live_color_control_job = None def _set_auto_color_button_enabled(self, enabled: bool, *, text: str | None = None) -> None: - def _apply() -> None: - try: - self.btn_auto_color.configure(state=("normal" if enabled else "disabled")) - if text is not None: - self.btn_auto_color.configure(text=text) - except Exception: - pass - self.after(0, _apply) + self.ui_queue.put(lambda: self._apply_button_state(enabled, text)) + + def _apply_button_state(self, enabled: bool, text: str | None = None) -> None: + try: + self.btn_auto_color.configure(state=("normal" if enabled else "disabled")) + if text is not None: + self.btn_auto_color.configure(text=text) + except Exception: + pass def _clear_live_color_control(self) -> None: try: @@ -601,8 +606,30 @@ def on_auto_mouth_color_adjust(self) -> None: self._auto_color_poll_job = self.after(120, self._poll_auto_color_result) # ----- logging (thread-safe) ----- - def log(self, s: str) -> None: - self.log_q.put(s) + def _poll_ui_queue(self): + try: + while True: + task = self.ui_queue.get_nowait() + task() + except queue.Empty: + pass + self.after(50, self._poll_ui_queue) + + def log(self, msg: str) -> None: + self.ui_queue.put(lambda: self._insert_log(msg)) + + def _insert_log(self, s: str) -> None: + # Remove null bytes from log text + s = s.replace("\x00", "") + self.txt.configure(state="normal") + self.txt.insert("end", s + "\n") + # 上限チェック + line_count = int(self.txt.index("end-1c").split(".")[0]) + if line_count > MAX_LOG_LINES: + excess = line_count - MAX_LOG_LINES + self.txt.delete("1.0", f"{excess + 1}.0") + self.txt.see("end") + self.txt.configure(state="disabled") def _save_session(self, payload: dict) -> bool: ok = save_session(payload) @@ -827,7 +854,7 @@ def _set_character(self, character: str, *, persist: bool = False) -> None: self._save_session({"character": character}) def _post_set_character(self, character: str, *, persist: bool = False) -> None: - self.after(0, lambda c=character, p=persist: self._set_character(c, persist=p)) + self.ui_queue.put(lambda c=character, p=persist: self._set_character(c, persist=p)) def _runtime_supports(self, runtime_py: str, flags: list[str]) -> bool: return script_contains(runtime_py, flags) @@ -864,7 +891,7 @@ def _apply(): self.soft_requested_at = None self.btn_stop.configure(text=STOP_BTN_TEXT_DEFAULT) - self.after(0, _apply) + self.ui_queue.put(_apply) def _set_running(self, running: bool) -> None: def _apply(): @@ -879,14 +906,14 @@ def _apply(): if not running: self._set_stop_mode("none") self._progress_reset() - self.after(0, _apply) + self.ui_queue.put(_apply) def _progress_reset(self) -> None: def _apply(): self.progress.configure(mode="determinate", maximum=1.0) self.progress_var.set(0.0) self.progress_text_var.set("待機中") - self.after(0, _apply) + self.ui_queue.put(_apply) def _progress_begin(self, total_steps: int, text: str) -> None: def _apply(): @@ -894,7 +921,7 @@ def _apply(): self.progress.configure(mode="determinate", maximum=self._progress_total) self.progress_var.set(0.0) self.progress_text_var.set(text) - self.after(0, _apply) + self.ui_queue.put(_apply) def _progress_step(self, step: int, text: str) -> None: def _apply(): @@ -902,13 +929,13 @@ def _apply(): val = min(max(0, int(step)), int(self._progress_total)) self.progress_var.set(val) self.progress_text_var.set(text) - self.after(0, _apply) + self.ui_queue.put(_apply) def _show_error(self, title: str, msg: str) -> None: - self.after(0, lambda: messagebox.showerror(title, msg)) + self.ui_queue.put(lambda: messagebox.showerror(title, msg)) def _show_warn(self, title: str, msg: str) -> None: - self.after(0, lambda: messagebox.showwarning(title, msg)) + self.ui_queue.put(lambda: messagebox.showwarning(title, msg)) def _apply_preview_selection(self, pad: float, coverage: float) -> None: pad_v = round(float(pad), 2) @@ -1503,9 +1530,8 @@ def _worker(): show_error=self._show_error, ) if selection.applied: - self.after( - 0, - lambda p=selection.pad, c=selection.coverage: self._apply_preview_selection(p, c), + self.ui_queue.put( + lambda p=selection.pad, c=selection.coverage: self._apply_preview_selection(p, c) ) except Exception as e: self._show_error("エラー", str(e)) diff --git a/pyproject.toml b/pyproject.toml index 025926f..d9076de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,89 +1,61 @@ -[build-system] -requires = ["setuptools>=60.0.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "motionpngtuber" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -requires-python = "==3.10.*" -dependencies = [ - "anime-face-detector==0.0.9 ; sys_platform == 'win32' or sys_platform == 'linux'", - "mmcv-full==1.7.0 ; sys_platform == 'win32' or sys_platform == 'linux'", - "mmdet==2.28.0 ; sys_platform == 'win32' or sys_platform == 'linux'", - "mmpose==0.29.0 ; sys_platform == 'win32' or sys_platform == 'linux'", - "numpy>=1.24.0,<2.0.0", - "opencv-python>=4.8.0", - "openmim>=0.3.9", - "pillow>=10.0.0", - "pip>=24.0", - "scipy>=1.10.0", - "setuptools>=60.0.0,<70.0.0", - "sounddevice>=0.4.6", - "torch==1.13.1+cu117 ; sys_platform == 'win32' or sys_platform == 'linux'", - "torchvision==0.14.1+cu117 ; sys_platform == 'win32' or sys_platform == 'linux'" -] - -[tool.uv] -override-dependencies = [ - # headless版を無効化してGUI版のopencv-pythonを使用 - "opencv-python-headless>=4.5.4.58 ; sys_platform == 'never'", -] -index-strategy = "first-index" - -[[tool.uv.index]] -name = "pytorch-cu117" -url = "https://download.pytorch.org/whl/cu117" -explicit = true - -[tool.uv.sources] -torch = [ - { index = "pytorch-cu117", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -torchvision = [ - { index = "pytorch-cu117", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -mmcv-full = [ - { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-manylinux1_x86_64.whl", marker = "sys_platform == 'linux'" }, - { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-win_amd64.whl", marker = "sys_platform == 'win32'" }, -] - -[tool.uv.extra-build-dependencies] -chumpy = ["wheel", "pip", "setuptools"] - -[project.scripts] -mouth-track-gui = "mouth_track_gui:main" -mouth-sprite-gui = "mouth_sprite_extractor_gui:main" -mouth-sprite-extract = "motionpngtuber.mouth_sprite_extractor:main" -auto-mouth-track = "auto_mouth_track_v2:main" -auto-erase-mouth = "auto_erase_mouth:main" -calibrate-mouth-track = "calibrate_mouth_track:main" -erase-mouth-offline = "erase_mouth_offline:main" -face-track-detect = "face_track_anime_detector:main" -convert-npz-to-json = "convert_npz_to_json:main" -lipsync-runtime = "loop_lipsync_runtime_patched_emotion_auto:main" - -[tool.setuptools] -packages = ["motionpngtuber", "mouth_track_gui"] -py-modules = [ - "auto_erase_mouth", - "auto_mouth_track_v2", - "calibrate_mouth_track", - "convert_npz_to_json", - "erase_mouth_offline", - "face_track_anime_detector", - "loop_lipsync_runtime_patched_emotion_auto", - "mouth_sprite_extractor", - "mouth_sprite_extractor_gui", - "mouth_track_gui", -] - -[tool.pytest.ini_options] -testpaths = ["tests"] -pythonpath = ["."] - -[dependency-groups] -dev = [ - "pytest>=9.0.3", -] +# ============================================================================= +# MotionPNGTuber - macOS (Apple Silicon) 用 pyproject.toml +# ============================================================================= +# +# インストール手順: +# 1. cp pyproject.toml pyproject.win.toml && cp pyproject.macos.toml pyproject.toml +# 2. uv venv .venv && uv sync +# 3. uv pip install pip setuptools wheel torch==2.0.1 torchvision==0.15.2 +# 4. xtcocotoolsをソースからビルド(PyPI版はビルドエラーになるため先に入れる): +# mkdir -p deps && cd deps +# git clone https://github.com/jin-s13/xtcocoapi.git +# cd xtcocoapi +# ../../.venv/bin/python -m pip install -e . +# cd ../.. +# 5. mmcv-fullをソースからビルド(ビルド済みバイナリがないため): +# cd deps +# curl -L https://github.com/open-mmlab/mmcv/archive/refs/tags/v1.7.0.tar.gz -o mmcv-1.7.0.tar.gz +# tar xzf mmcv-1.7.0.tar.gz +# cd mmcv-1.7.0 +# MMCV_WITH_OPS=1 FORCE_CUDA=0 ../../.venv/bin/python setup.py develop +# MMCV_WITH_OPS=1 FORCE_CUDA=0 ../../.venv/bin/python setup.py build_ext --inplace +# cd ../.. +# 6. anime-face-detectorをインストール(xtcocotools/mmcvは既にあるのでスキップされる): +# (--no-build-isolation: chumpy等のビルドに仮想環境のpipが必要なため) +# uv pip install --no-build-isolation anime-face-detector +# 7. mmdet/mmposeを正しいバージョンに修正(anime-face-detectorが最新版を入れてしまうため): +# uv pip install mmdet==2.28.0 mmpose==0.29.0 +# 8. .venv/bin/python mouth_track_gui.py +# +# 注意: +# - deps/ ディレクトリは削除しないこと(editable installのため参照される) +# - PyTorchは2.0.1を使用(最新版はmmcv 1.7.0と互換性がない) +# +# ============================================================================= + +[project] +name = "motionpngtuber" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = "==3.10.*" +dependencies = [ + # 基本パッケージ(uv syncでインストール) + "numpy>=1.24.0,<2.0.0", + "opencv-python>=4.8.0", + "pillow>=10.0.0", + "scipy>=1.10.0", + "sounddevice>=0.4.6", + # 以下は手動インストール(手順2-5参照): + # - pip, setuptools, wheel + # - torch==2.0.1, torchvision==0.15.2 + # - anime-face-detector, mmdet==2.28.0, mmpose==0.29.0 + # - mmcv-full (ソースビルド) + # - xtcocotools (ソースビルド) +] + +[tool.uv] +override-dependencies = [ + # headless版を無効化してGUI版のopencv-pythonを使用 + "opencv-python-headless>=4.5.4.58 ; sys_platform == 'never'", +] diff --git a/uv.lock b/uv.lock index 4832da0..f07ee66 100644 --- a/uv.lock +++ b/uv.lock @@ -12,42 +12,6 @@ resolution-markers = [ [manifest] overrides = [{ name = "opencv-python-headless", marker = "sys_platform == 'never'", specifier = ">=4.5.4.58" }] -[[package]] -name = "addict" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186, upload-time = "2020-11-21T16:21:31.416Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, -] - -[[package]] -name = "anime-face-detector" -version = "0.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mmcv-full", version = "1.7.0", source = { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-manylinux1_x86_64.whl" }, marker = "sys_platform == 'linux'" }, - { name = "mmcv-full", version = "1.7.0", source = { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-win_amd64.whl" }, marker = "sys_platform == 'win32'" }, - { name = "mmdet", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "mmpose", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/11/3d/819956db5c6223d66ddb26843260c820201bcf06f3841880b24f6d63839d/anime-face-detector-0.0.9.tar.gz", hash = "sha256:eba158d66e75bf3a32f72fd2a1dfca0b29e5d5124f6cf0e1487aeb0845333d9f", size = 10622, upload-time = "2022-05-18T21:10:00.699Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/b8/d12179c6d3c24047a6be554235fdfafa8e864a00279a332726f93a1e7d6f/anime_face_detector-0.0.9-py3-none-any.whl", hash = "sha256:a242d503d3a4ed67b9afa4d71ec686bae4372c33a2de1ddc3eca6a5ecc2e8da7", size = 10016, upload-time = "2022-05-18T21:09:58.732Z" }, -] - -[[package]] -name = "certifi" -version = "2022.12.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/f7/2b1b0ec44fdc30a3d31dfebe52226be9ddc40cd6c0f34ffc8923ba423b69/certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", size = 156897, upload-time = "2022-12-07T20:13:22.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18", size = 155255, upload-time = "2022-12-07T20:13:19.428Z" }, -] - [[package]] name = "cffi" version = "2.0.0" @@ -71,466 +35,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, ] -[[package]] -name = "charset-normalizer" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/34/44964211e5410b051e4b8d2869c470ae8a68ae274953b1c7de6d98bbcf94/charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", size = 82360, upload-time = "2022-08-19T22:13:48.372Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f", size = 39748, upload-time = "2022-08-19T22:13:46.702Z" }, -] - -[[package]] -name = "chumpy" -version = "0.70" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "scipy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "six", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/f7/865755c8bdb837841938de622e6c8b5cb6b1c933bde3bd3332f0cd4574f1/chumpy-0.70.tar.gz", hash = "sha256:a0275c2018784ca1302875567dc81761f5fd469fab9f3ac0f3e7c39e9180350a", size = 50608, upload-time = "2020-08-26T00:05:32.216Z" } - -[[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "contourpy" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, - { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, - { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, - { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, - { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, - { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, -] - -[[package]] -name = "cycler" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, -] - -[[package]] -name = "cython" -version = "3.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/85/7574c9cd44b69a27210444b6650f6477f56c75fee1b70d7672d3e4166167/cython-3.2.4.tar.gz", hash = "sha256:84226ecd313b233da27dc2eb3601b4f222b8209c3a7216d8733b031da1dc64e6", size = 3280291, upload-time = "2026-01-04T14:14:14.473Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/3d/b26f29092c71c36e0462752885bdfb18c23c176af4de953fdae2772a8941/cython-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f136f379a4a54246facd0eb6f1ee15c3837cb314ce87b677582ec014db4c6845", size = 3370134, upload-time = "2026-01-04T14:14:53.627Z" }, - { url = "https://files.pythonhosted.org/packages/56/9e/539fb0d09e4f5251b5b14f8daf77e71fee021527f1013791038234618b6b/cython-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35ab0632186057406ec729374c737c37051d2eacad9d515d94e5a3b3e58a9b02", size = 3537552, upload-time = "2026-01-04T14:14:56.852Z" }, - { url = "https://files.pythonhosted.org/packages/10/c6/82d19a451c050d1be0f05b1a3302267463d391db548f013ee88b5348a8e9/cython-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:ca2399dc75796b785f74fb85c938254fa10c80272004d573c455f9123eceed86", size = 2766191, upload-time = "2026-01-04T14:14:58.709Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/48530d9b9d64ec11dbe0dd3178a5fe1e0b27977c1054ecffb82be81e9b6a/cython-3.2.4-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6d5267f22b6451eb1e2e1b88f6f78a2c9c8733a6ddefd4520d3968d26b824581", size = 3210669, upload-time = "2026-01-04T14:15:41.911Z" }, - { url = "https://files.pythonhosted.org/packages/5e/91/4865fbfef1f6bb4f21d79c46104a53d1a3fa4348286237e15eafb26e0828/cython-3.2.4-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b6e58f73a69230218d5381817850ce6d0da5bb7e87eb7d528c7027cbba40b06", size = 2856835, upload-time = "2026-01-04T14:15:43.815Z" }, - { url = "https://files.pythonhosted.org/packages/fa/39/60317957dbef179572398253f29d28f75f94ab82d6d39ea3237fb6c89268/cython-3.2.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e71efb20048358a6b8ec604a0532961c50c067b5e63e345e2e359fff72feaee8", size = 2994408, upload-time = "2026-01-04T14:15:45.422Z" }, - { url = "https://files.pythonhosted.org/packages/8d/30/7c24d9292650db4abebce98abc9b49c820d40fa7c87921c0a84c32f4efe7/cython-3.2.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:28b1e363b024c4b8dcf52ff68125e635cb9cb4b0ba997d628f25e32543a71103", size = 2891478, upload-time = "2026-01-04T14:15:47.394Z" }, - { url = "https://files.pythonhosted.org/packages/86/70/03dc3c962cde9da37a93cca8360e576f904d5f9beecfc9d70b1f820d2e5f/cython-3.2.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:31a90b4a2c47bb6d56baeb926948348ec968e932c1ae2c53239164e3e8880ccf", size = 3225663, upload-time = "2026-01-04T14:15:49.446Z" }, - { url = "https://files.pythonhosted.org/packages/b1/97/10b50c38313c37b1300325e2e53f48ea9a2c078a85c0c9572057135e31d5/cython-3.2.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e65e4773021f8dc8532010b4fbebe782c77f9a0817e93886e518c93bd6a44e9d", size = 3115628, upload-time = "2026-01-04T14:15:51.323Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b1/d6a353c9b147848122a0db370863601fdf56de2d983b5c4a6a11e6ee3cd7/cython-3.2.4-cp39-abi3-win32.whl", hash = "sha256:2b1f12c0e4798293d2754e73cd6f35fa5bbdf072bdc14bc6fc442c059ef2d290", size = 2437463, upload-time = "2026-01-04T14:15:53.787Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d8/319a1263b9c33b71343adfd407e5daffd453daef47ebc7b642820a8b68ed/cython-3.2.4-cp39-abi3-win_arm64.whl", hash = "sha256:3b8e62049afef9da931d55de82d8f46c9a147313b69d5ff6af6e9121d545ce7a", size = 2442754, upload-time = "2026-01-04T14:15:55.382Z" }, - { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "fonttools" -version = "4.61.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, - { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, - { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, - { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, -] - -[[package]] -name = "idna" -version = "3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", size = 183077, upload-time = "2022-09-14T00:24:27.719Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2", size = 61538, upload-time = "2022-09-14T00:24:23.22Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "json-tricks" -version = "3.17.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/15/a4983fd782472b7968b4a0b50092981debccdd938ba4d94698bbb71b0abb/json_tricks-3.17.3.tar.gz", hash = "sha256:71561eedad7c22dde019e9a38ff8c46ebd91da789e31e2513f627dd2cbbdbf56", size = 30537, upload-time = "2023-08-19T12:08:29.487Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/fd/e3edcf827e7f9c17c5ea1a192841dcfb1dd575a7518c25c5cadd921625b1/json_tricks-3.17.3-py2.py3-none-any.whl", hash = "sha256:8ba11cb66a09532945c05c7374a72b857dfc3870b2d145125edd508f4027dff9", size = 27613, upload-time = "2023-08-19T12:08:27.533Z" }, -] - -[[package]] -name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, - { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, - { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, - { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, - { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, - { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, -] - -[[package]] -name = "markdown" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, -] - -[[package]] -name = "matplotlib" -version = "3.10.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contourpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "cycler", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "fonttools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "kiwisolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "packaging", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pillow", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pyparsing", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "python-dateutil", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, - { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -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 = [ - { 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 = "mmcv-full" -version = "1.7.0" -source = { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-manylinux1_x86_64.whl" } -resolution-markers = [ - "platform_machine == 'aarch64' and sys_platform == 'linux'", - "platform_machine != 'aarch64' and sys_platform == 'linux'", -] -dependencies = [ - { name = "addict", marker = "sys_platform == 'linux'" }, - { name = "numpy", marker = "sys_platform == 'linux'" }, - { name = "opencv-python", marker = "sys_platform == 'linux'" }, - { name = "packaging", marker = "sys_platform == 'linux'" }, - { name = "pillow", marker = "sys_platform == 'linux'" }, - { name = "pyyaml", marker = "sys_platform == 'linux'" }, - { name = "yapf", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:017e7870bbe2798b117abb4ae6f3e9412d7cc1c4e2247db316381ab821609f0c" }, -] - -[package.metadata] -requires-dist = [ - { name = "addict" }, - { name = "addict", marker = "extra == 'all'" }, - { name = "coverage", marker = "extra == 'all'" }, - { name = "coverage", marker = "extra == 'tests'" }, - { name = "lmdb", marker = "extra == 'all'" }, - { name = "lmdb", marker = "extra == 'tests'" }, - { name = "ninja", marker = "extra == 'all'" }, - { name = "ninja", marker = "extra == 'optional'" }, - { name = "numpy" }, - { name = "numpy", marker = "extra == 'all'" }, - { name = "onnx", marker = "extra == 'all'" }, - { name = "onnx", marker = "extra == 'tests'" }, - { name = "onnxoptimizer", marker = "python_full_version < '3.10' and extra == 'all'" }, - { name = "onnxoptimizer", marker = "python_full_version < '3.10' and extra == 'tests'" }, - { name = "onnxruntime", marker = "extra == 'all'", specifier = ">=1.8.0" }, - { name = "onnxruntime", marker = "extra == 'tests'", specifier = ">=1.8.0" }, - { name = "opencv-python", specifier = ">=3" }, - { name = "packaging" }, - { name = "packaging", marker = "extra == 'all'" }, - { name = "pillow" }, - { name = "pillow", marker = "extra == 'all'" }, - { name = "protobuf", marker = "extra == 'all'", specifier = "~=3.19.0" }, - { name = "protobuf", marker = "extra == 'tests'", specifier = "~=3.19.0" }, - { name = "psutil", marker = "extra == 'all'" }, - { name = "psutil", marker = "extra == 'optional'" }, - { name = "pytest", marker = "extra == 'all'" }, - { name = "pytest", marker = "extra == 'tests'" }, - { name = "pytest-runner", marker = "extra == 'all'" }, - { name = "pytest-runner", marker = "extra == 'build'" }, - { name = "pyturbojpeg", marker = "extra == 'all'" }, - { name = "pyturbojpeg", marker = "extra == 'tests'" }, - { name = "pyyaml" }, - { name = "pyyaml", marker = "extra == 'all'" }, - { name = "regex", marker = "sys_platform == 'win32'" }, - { name = "regex", marker = "sys_platform == 'win32' and extra == 'all'" }, - { name = "scipy", marker = "extra == 'all'" }, - { name = "scipy", marker = "extra == 'tests'" }, - { name = "tifffile", marker = "extra == 'all'" }, - { name = "tifffile", marker = "extra == 'tests'" }, - { name = "yapf" }, - { name = "yapf", marker = "extra == 'all'" }, -] -provides-extras = ["all", "build", "optional", "tests"] - -[[package]] -name = "mmcv-full" -version = "1.7.0" -source = { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-win_amd64.whl" } -resolution-markers = [ - "sys_platform == 'win32'", -] -dependencies = [ - { name = "addict", marker = "sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'win32'" }, - { name = "packaging", marker = "sys_platform == 'win32'" }, - { name = "pillow", marker = "sys_platform == 'win32'" }, - { name = "pyyaml", marker = "sys_platform == 'win32'" }, - { name = "regex", marker = "sys_platform == 'win32'" }, - { name = "yapf", marker = "sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:18676de082bf458a25772d073204504fdf907e1d612e7bbcfa63eb763ec9a54b" }, -] - -[package.metadata] -requires-dist = [ - { name = "addict" }, - { name = "addict", marker = "extra == 'all'" }, - { name = "coverage", marker = "extra == 'all'" }, - { name = "coverage", marker = "extra == 'tests'" }, - { name = "lmdb", marker = "extra == 'all'" }, - { name = "lmdb", marker = "extra == 'tests'" }, - { name = "ninja", marker = "extra == 'all'" }, - { name = "ninja", marker = "extra == 'optional'" }, - { name = "numpy" }, - { name = "numpy", marker = "extra == 'all'" }, - { name = "onnx", marker = "extra == 'all'" }, - { name = "onnx", marker = "extra == 'tests'" }, - { name = "onnxoptimizer", marker = "python_full_version < '3.10' and extra == 'all'" }, - { name = "onnxoptimizer", marker = "python_full_version < '3.10' and extra == 'tests'" }, - { name = "onnxruntime", marker = "extra == 'all'", specifier = ">=1.8.0" }, - { name = "onnxruntime", marker = "extra == 'tests'", specifier = ">=1.8.0" }, - { name = "packaging" }, - { name = "packaging", marker = "extra == 'all'" }, - { name = "pillow" }, - { name = "pillow", marker = "extra == 'all'" }, - { name = "protobuf", marker = "extra == 'all'", specifier = "~=3.19.0" }, - { name = "protobuf", marker = "extra == 'tests'", specifier = "~=3.19.0" }, - { name = "psutil", marker = "extra == 'all'" }, - { name = "psutil", marker = "extra == 'optional'" }, - { name = "pytest", marker = "extra == 'all'" }, - { name = "pytest", marker = "extra == 'tests'" }, - { name = "pytest-runner", marker = "extra == 'all'" }, - { name = "pytest-runner", marker = "extra == 'build'" }, - { name = "pyturbojpeg", marker = "extra == 'all'" }, - { name = "pyturbojpeg", marker = "extra == 'tests'" }, - { name = "pyyaml" }, - { name = "pyyaml", marker = "extra == 'all'" }, - { name = "regex", marker = "sys_platform == 'win32'" }, - { name = "regex", marker = "sys_platform == 'win32' and extra == 'all'" }, - { name = "scipy", marker = "extra == 'all'" }, - { name = "scipy", marker = "extra == 'tests'" }, - { name = "tifffile", marker = "extra == 'all'" }, - { name = "tifffile", marker = "extra == 'tests'" }, - { name = "yapf" }, - { name = "yapf", marker = "extra == 'all'" }, -] -provides-extras = ["all", "build", "optional", "tests"] - -[[package]] -name = "mmdet" -version = "2.28.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pycocotools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "scipy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "six", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "terminaltables", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/9f/6c25e5c919e1f42bedec079d0a584aebd440568cbe4bbe54bc0b38abfe04/mmdet-2.28.0.tar.gz", hash = "sha256:a58d2ebbb238521ff11aab67cfabced9af96df4729a8f495fc8a0c4aee76c432", size = 820534, upload-time = "2023-01-28T13:00:03.992Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/2d/203c9e521b67990d498ee2784a6d5c290202562852e384ba761b9f885234/mmdet-2.28.0-py3-none-any.whl", hash = "sha256:984a21caa036d8af7e86bc2e69b9973a7adb29a3544a9eda8efff2af73ecd592", size = 1493758, upload-time = "2023-01-28T13:00:02.292Z" }, -] - -[[package]] -name = "mmpose" -version = "0.29.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "chumpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "json-tricks", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "matplotlib", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "munkres", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "opencv-python", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pillow", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "scipy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "xtcocotools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/44/bf/be2237849fcafb64fe6ee499af9aabce21895aeaa0d3d952bfc7094ed2ca/mmpose-0.29.0.tar.gz", hash = "sha256:0a2087b598a9e6e361b65c2813412206026d5755c6589218dd1ec5ab8a03d7f0", size = 567741, upload-time = "2022-10-14T13:39:33.144Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/08/1ccf91a715943973b5587ca2569acd33990453af4c59d591e14781747883/mmpose-0.29.0-py2.py3-none-any.whl", hash = "sha256:588c6ca4eb4882b2131f25ba961587c8e231b972d5a123362454b2b2f5499c41", size = 1608564, upload-time = "2022-10-14T13:39:30.978Z" }, -] - -[[package]] -name = "model-index" -version = "0.1.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "markdown" }, - { name = "ordered-set" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/91/3db595e51266e5a32f4a26e3b4c4212ba83b4ce649196e81565cf0dcdec2/model-index-0.1.11.tar.gz", hash = "sha256:2f9870200f3b00813881b07b90d2c67291663534e43915c75fe476b6977bf2ad", size = 20495, upload-time = "2021-03-22T14:26:30.814Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/a6/4d4cbbef704f186d143e2859296a610a355992e4eae71582bd598093b36a/model_index-0.1.11-py3-none-any.whl", hash = "sha256:a2a4d4431cd44e571d31e223cc4b0432663a62689de453bdb666e56a514b0e07", size = 34393, upload-time = "2021-03-22T14:26:29.63Z" }, -] - [[package]] name = "motionpngtuber" version = "0.1.0" -source = { editable = "." } +source = { virtual = "." } dependencies = [ - { name = "anime-face-detector", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "mmcv-full", version = "1.7.0", source = { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-manylinux1_x86_64.whl" }, marker = "sys_platform == 'linux'" }, - { name = "mmcv-full", version = "1.7.0", source = { url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-win_amd64.whl" }, marker = "sys_platform == 'win32'" }, - { name = "mmdet", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "mmpose", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "numpy" }, { name = "opencv-python" }, - { name = "openmim" }, { name = "pillow" }, - { name = "pip" }, { name = "scipy" }, - { name = "setuptools" }, { name = "sounddevice" }, - { name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, ] [package.metadata] requires-dist = [ - { name = "anime-face-detector", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==0.0.9" }, - { name = "mmcv-full", marker = "sys_platform == 'linux'", url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-manylinux1_x86_64.whl" }, - { name = "mmcv-full", marker = "sys_platform == 'win32'", url = "https://download.openmmlab.com/mmcv/dist/cu117/torch1.13.0/mmcv_full-1.7.0-cp310-cp310-win_amd64.whl" }, - { name = "mmdet", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==2.28.0" }, - { name = "mmpose", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==0.29.0" }, { name = "numpy", specifier = ">=1.24.0,<2.0.0" }, { name = "opencv-python", specifier = ">=4.8.0" }, - { name = "openmim", specifier = ">=0.3.9" }, { name = "pillow", specifier = ">=10.0.0" }, - { name = "pip", specifier = ">=24.0" }, { name = "scipy", specifier = ">=1.10.0" }, - { name = "setuptools", specifier = ">=60.0.0,<70.0.0" }, { name = "sounddevice", specifier = ">=0.4.6" }, - { name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==1.13.1+cu117", index = "https://download.pytorch.org/whl/cu117" }, - { name = "torchvision", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==0.14.1+cu117", index = "https://download.pytorch.org/whl/cu117" }, -] - -[package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=9.0.3" }] - -[[package]] -name = "munkres" -version = "1.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/41/6a3d0ef908f47d07c31e5d1c2504388c27c39b10b8cf610175b5a789a5c1/munkres-1.1.4.tar.gz", hash = "sha256:fc44bf3c3979dada4b6b633ddeeb8ffbe8388ee9409e4d4e8310c2da1792db03", size = 14047, upload-time = "2020-09-15T15:12:20.956Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/ab/0301c945a704218bc9435f0e3c88884f6b19ef234d8899fb47ce1ccfd0c9/munkres-1.1.4-py2.py3-none-any.whl", hash = "sha256:6b01867d4a8480d865aea2326e4b8f7c46431e9e55b4a2e32d989307d7bced2a", size = 7015, upload-time = "2020-09-15T15:12:19.627Z" }, ] [[package]] @@ -566,93 +89,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" }, ] -[[package]] -name = "opendatalab" -version = "0.0.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama" }, - { name = "openxlab" }, - { name = "pycryptodome" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "requests" }, - { name = "rich" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/9f/25bfae72e3d10040f6ba80e2b0b9688c9477528b2aed1fe871847f48e479/opendatalab-0.0.10.tar.gz", hash = "sha256:9b1382f974bd76a961747dc33308fce5b024d337c02cf3acb728c64952ca9aaf", size = 23615, upload-time = "2023-08-02T07:30:08.352Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/82/28fa3a91b7c4852fbad9ad32c7b49e4b1e212ab7ccf7296736da0935070d/opendatalab-0.0.10-py3-none-any.whl", hash = "sha256:b6a317785b7db418739933d4af6d981a0e45f6cf20a3e113bef63ed9b4488251", size = 29506, upload-time = "2023-08-02T07:29:58.629Z" }, -] - -[[package]] -name = "openmim" -version = "0.3.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama" }, - { name = "model-index" }, - { name = "opendatalab" }, - { name = "pandas" }, - { name = "pip" }, - { name = "requests" }, - { name = "rich" }, - { name = "tabulate" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/00/d96fcf6808fa2648e037fa45c764c58215ec797ab38e2b02bdf1d24c9333/openmim-0.3.9.tar.gz", hash = "sha256:b3977b92232b4b8c4d987cbc73e4515826d5543ccd3a66d49fcfc602cc5b3352", size = 48666, upload-time = "2023-06-28T07:45:45.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/b3/95531cee452028869d0e08974561f83e9c256c98f62c7a45a51893a61c54/openmim-0.3.9-py2.py3-none-any.whl", hash = "sha256:71581498b68767f8e1f340575b91c9994ccc93656901639f11300e46247da263", size = 52660, upload-time = "2023-06-28T07:45:42.832Z" }, -] - -[[package]] -name = "openxlab" -version = "0.0.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/60/542576bf98aa8ef8939febbe6a112b2e9b138d188a64000ae692aa6eb747/openxlab-0.0.11.tar.gz", hash = "sha256:bacc3ff3052432f4c3241cab7711e2cd6a4c4b80605dc3e8f2c157eddabd0cda", size = 14703692, upload-time = "2023-06-19T02:47:41.316Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/20/cd2ffb50efceac4fe4d9193e2ce5f7e00813cc10203c1a714d3247452d9c/openxlab-0.0.11-py3-none-any.whl", hash = "sha256:ab594a0f8c6f74501ab6c82a823c17bd2d038f72ee47a41fbac2c801df68565a", size = 55314, upload-time = "2023-06-19T02:47:21.032Z" }, -] - -[[package]] -name = "ordered-set" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" }, -] - -[[package]] -name = "packaging" -version = "24.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788, upload-time = "2024-06-09T23:19:24.956Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985, upload-time = "2024-06-09T23:19:21.909Z" }, -] - -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, - { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, - { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, - { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, -] - [[package]] name = "pillow" version = "12.0.0" @@ -672,56 +108,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7", size = 2435903, upload-time = "2025-10-15T18:21:46.29Z" }, ] -[[package]] -name = "pip" -version = "25.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014, upload-time = "2025-10-25T00:55:41.394Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -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" }, -] - -[[package]] -name = "pycocotools" -version = "2.0.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/df/32354b5dda963ffdfc8f75c9acf8828ef7890723a4ed57bb3ff2dc1d6f7e/pycocotools-2.0.11.tar.gz", hash = "sha256:34254d76da85576fcaf5c1f3aa9aae16b8cb15418334ba4283b800796bd1993d", size = 25381, upload-time = "2025-12-15T22:31:46.148Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/fe/861db6515824815eaabce27734653a6b100ddb22364b3345dd862b2c5b65/pycocotools-2.0.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca9f120f719ec405ad0c74ccfdb8402b0c37bd5f88ab5b6482a0de2efd5a36f4", size = 463947, upload-time = "2025-12-15T22:30:55.419Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a1/b4b49b85763043372e66baa10dffa42337cf4687d6db22546c27f3a4d732/pycocotools-2.0.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e40a3a898c6e5340b8d70cf7984868b9bff8c3d80187de9a3b661d504d665978", size = 472455, upload-time = "2025-12-15T22:30:56.895Z" }, - { url = "https://files.pythonhosted.org/packages/48/70/fac670296e6a2b45eb7434d0480b9af6cb85a8de4f4848b49b01154bc859/pycocotools-2.0.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7cd4cdfd2c676f30838aa0b1047441892fb4f97d70bf3df480bcc7a18a64d7d4", size = 457911, upload-time = "2025-12-15T22:30:58.377Z" }, - { url = "https://files.pythonhosted.org/packages/33/f5/6158de63354dfcb677c8da34a4d205cc532e3277338ab7e6dea1310ba8de/pycocotools-2.0.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08c79789fd79e801ae4ecfcfeec32b31e36254e7a2b4019af28c104975d5e730", size = 476472, upload-time = "2025-12-15T22:30:59.736Z" }, - { url = "https://files.pythonhosted.org/packages/fc/01/46d2a782cda19ba1beb7c431f417e1e478f0bf1273fa5fe5d10de7c18d76/pycocotools-2.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:f78cbb1a32d061fcad4bdba083de70a39a21c1c3d9235a3f77d8f007541ec5ef", size = 80165, upload-time = "2025-12-15T22:31:00.886Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5c/6bd945781bb04c2148929183d1d67b05ce07996313b0f87bb88c6a805493/pycocotools-2.0.11-cp310-cp310-win_arm64.whl", hash = "sha256:e21311ea71f85591680d8992858e2d44a2a156dc3b2bf1c5c901c4a19348177b", size = 69358, upload-time = "2025-12-15T22:31:01.815Z" }, - { url = "https://files.pythonhosted.org/packages/63/3c/68d7ea376aada9046e7ea2d7d0dad0d27e1ae8b4b3c26a28346689390ab2/pycocotools-2.0.11-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fd4121766cc057133534679c0ec3f9023dbd96e9b31cf95c86a069ebdac2b65", size = 398434, upload-time = "2025-12-15T22:31:12.558Z" }, - { url = "https://files.pythonhosted.org/packages/23/59/dc81895beff4e1207a829d40d442ea87cefaac9f6499151965f05c479619/pycocotools-2.0.11-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a82d1c9ed83f75da0b3f244f2a3cf559351a283307bd9b79a4ee2b93ab3231dd", size = 411685, upload-time = "2025-12-15T22:31:13.995Z" }, - { url = "https://files.pythonhosted.org/packages/0b/0b/5a8a7de300862a2eb5e2ecd3cb015126231379206cd3ebba8f025388d770/pycocotools-2.0.11-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:89e853425018e2c2920ee0f2112cf7c140a1dcf5f4f49abd9c2da112c3e0f4b3", size = 390500, upload-time = "2025-12-15T22:31:15.138Z" }, - { url = "https://files.pythonhosted.org/packages/63/b5/519bb68647f06feea03d5f355c33c05800aeae4e57b9482b2859eb00752e/pycocotools-2.0.11-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:87af87b8d06d5b852a885a319d9362dca3bed9f8bbcc3feb6513acb1f88ea242", size = 409790, upload-time = "2025-12-15T22:31:16.326Z" }, - { url = "https://files.pythonhosted.org/packages/83/b4/f6708404ff494706b80e714b919f76dc4ec9845a4007affd6d6b0843f928/pycocotools-2.0.11-cp312-abi3-win_amd64.whl", hash = "sha256:ffe806ce535f5996445188f9a35643791dc54beabc61bd81e2b03367356d604f", size = 77570, upload-time = "2025-12-15T22:31:17.703Z" }, - { url = "https://files.pythonhosted.org/packages/6e/63/778cd0ddc9d4a78915ac0a72b56d7fb204f7c3fabdad067d67ea0089762e/pycocotools-2.0.11-cp312-abi3-win_arm64.whl", hash = "sha256:c230f5e7b14bd19085217b4f40bba81bf14a182b150b8e9fab1c15d504ade343", size = 64564, upload-time = "2025-12-15T22:31:18.652Z" }, -] - [[package]] name = "pycparser" version = "2.23" @@ -731,153 +117,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] -[[package]] -name = "pycryptodome" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, - { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, - { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, - { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, - { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, - { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, - { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, - { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, - { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "pyparsing" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, - { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, -] - -[[package]] -name = "regex" -version = "2025.11.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669, upload-time = "2025-11-03T21:34:22.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/cc/90ab0fdbe6dce064a42015433f9152710139fb04a8b81b4fb57a1cb63ffa/regex-2025.11.3-cp310-cp310-win32.whl", hash = "sha256:149eb0bba95231fb4f6d37c8f760ec9fa6fabf65bab555e128dde5f2475193ec", size = 265802, upload-time = "2025-11-03T21:31:06.581Z" }, - { url = "https://files.pythonhosted.org/packages/34/9d/e9e8493a85f3b1ddc4a5014465f5c2b78c3ea1cbf238dcfde78956378041/regex-2025.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:ee3a83ce492074c35a74cc76cf8235d49e77b757193a5365ff86e3f2f93db9fd", size = 277722, upload-time = "2025-11-03T21:31:08.144Z" }, - { url = "https://files.pythonhosted.org/packages/15/c4/b54b24f553966564506dbf873a3e080aef47b356a3b39b5d5aba992b50db/regex-2025.11.3-cp310-cp310-win_arm64.whl", hash = "sha256:38af559ad934a7b35147716655d4a2f79fcef2d695ddfe06a06ba40ae631fa7e", size = 270289, upload-time = "2025-11-03T21:31:10.267Z" }, -] - -[[package]] -name = "requests" -version = "2.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/61/a867851fd5ab77277495a8709ddda0861b28163c4613b011bc00228cc724/requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", size = 109805, upload-time = "2022-06-29T15:13:42.715Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349", size = 62843, upload-time = "2022-06-29T15:13:40.685Z" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, -] - [[package]] name = "scipy" version = "1.15.3" @@ -898,24 +137,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, ] -[[package]] -name = "setuptools" -version = "69.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/4f/b10f707e14ef7de524fe1f8988a294fb262a29c9b5b12275c7e188864aed/setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987", size = 2291314, upload-time = "2024-04-13T21:06:28.589Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/29/13965af254e3373bceae8fb9a0e6ea0d0e571171b80d6646932131d6439b/setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32", size = 894566, upload-time = "2024-04-13T21:06:23.256Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - [[package]] name = "sounddevice" version = "0.5.3" @@ -930,126 +151,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f5/74/52186e3e5c833d00273f7949a9383adff93692c6e02406bf359cb4d3e921/sounddevice-0.5.3-py3-none-win32.whl", hash = "sha256:845d6927bcf14e84be5292a61ab3359cf8e6b9145819ec6f3ac2619ff089a69c", size = 312882, upload-time = "2025-10-19T13:23:54.829Z" }, { url = "https://files.pythonhosted.org/packages/66/c7/16123d054aef6d445176c9122bfbe73c11087589b2413cab22aff5a7839a/sounddevice-0.5.3-py3-none-win_amd64.whl", hash = "sha256:f55ad20082efc2bdec06928e974fbcae07bc6c405409ae1334cefe7d377eb687", size = 364025, upload-time = "2025-10-19T13:23:56.362Z" }, ] - -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - -[[package]] -name = "terminaltables" -version = "3.1.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/fc/0b73d782f5ab7feba8d007573a3773c58255f223c5940a7b7085f02153c3/terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543", size = 12264, upload-time = "2021-12-07T19:03:35.758Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/fb/ea621e0a19733e01fe4005d46087d383693c0f4a8f824b47d8d4122c87e0/terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874", size = 15155, upload-time = "2021-12-07T19:03:34.013Z" }, -] - -[[package]] -name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, -] - -[[package]] -name = "torch" -version = "1.13.1+cu117" -source = { registry = "https://download.pytorch.org/whl/cu117" } -dependencies = [ - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cu117/torch-1.13.1%2Bcu117-cp310-cp310-linux_x86_64.whl", hash = "sha256:14c5c9db09df8cf1b3942a3479c779da6e293a84a162d8a6ac71e2bde30e30c5" }, - { url = "https://download-r2.pytorch.org/whl/cu117/torch-1.13.1%2Bcu117-cp310-cp310-win_amd64.whl", hash = "sha256:978239684c6ec455ad2157ff33d44fdb9dd8d3a93b9d2f4ac7aa57691e990136" }, -] - -[[package]] -name = "torchvision" -version = "0.14.1+cu117" -source = { registry = "https://download.pytorch.org/whl/cu117" } -dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pillow", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "requests", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cu117/torchvision-0.14.1%2Bcu117-cp310-cp310-linux_x86_64.whl", hash = "sha256:83d271a530392814b8c7f69382285cac7c7da79b8f5dcc6bcf56c628547bce53" }, - { url = "https://download-r2.pytorch.org/whl/cu117/torchvision-0.14.1%2Bcu117-cp310-cp310-win_amd64.whl", hash = "sha256:b39fc67e7131053d435804d7901e88528611c0832fd9f1cc26476b5a27cc5d81" }, -] - -[[package]] -name = "tqdm" -version = "4.66.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504, upload-time = "2024-08-03T22:35:40.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351, upload-time = "2024-08-03T22:35:36.644Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "tzdata" -version = "2025.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, -] - -[[package]] -name = "urllib3" -version = "1.26.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/51/32da03cf19d17d46cce5c731967bf58de9bd71db3a379932f53b094deda4/urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8", size = 300476, upload-time = "2022-11-23T22:34:32.145Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/0c/cc6644eaa594585e5875f46f3c83ee8762b647b51fc5b0fb253a242df2dc/urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", size = 140572, upload-time = "2022-11-23T22:34:29.785Z" }, -] - -[[package]] -name = "xtcocotools" -version = "1.14.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cython", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "matplotlib", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "setuptools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/79/ff409182e7c6b49299cbd7ee9ac8a14bb7174827b8c0616248fd897cf5c0/xtcocotools-1.14.3.tar.gz", hash = "sha256:6434382784d206204d2a1b491dba359ff3464a2b9eb5890fbc0d868cad4e810f", size = 28828, upload-time = "2023-10-19T07:52:25.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/0b/c67774d4101fd2c79bb2d0ef9246518222cfd78fde1ff16e38cd1f600026/xtcocotools-1.14.3-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:34e3f4aa593a97b0f34cf97b0a21d01d939f7b36d7b79b828da2985aee9f52e5", size = 436012, upload-time = "2023-10-19T07:52:29.482Z" }, - { url = "https://files.pythonhosted.org/packages/fd/05/84c3863331b1532748d9e82996e6457e05373e3668498811a93deab7c630/xtcocotools-1.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:a537c6fb1de97ea52097426e5429be75867795734272a1f05401fe0f1275374d", size = 88032, upload-time = "2023-10-19T07:54:10.141Z" }, -] - -[[package]] -name = "yapf" -version = "0.43.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "tomli", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, -]