""" 图片下载模块 - 卡牌标准图:从 cards_master_v2 的 img_url (腾讯云COS) 下载,HTTP 直接访问 - 交易图:从 MinIO 下载(需配置账号密码),或从本地目录读取 """ import os import hashlib import requests import cv2 import numpy as np def _url_to_filename(url): """URL 转本地文件名(md5 防冲突)""" return hashlib.md5(url.encode("utf-8")).hexdigest() + ".jpg" def download_card_image(url, save_dir, timeout=15, retries=2): """ 下载卡牌标准图(COS,HTTP 公开访问)。 已存在则直接返回路径(断点续传)。 Returns: 本地路径;失败返回 None """ fname = _url_to_filename(url) path = os.path.join(save_dir, fname) if os.path.exists(path) and os.path.getsize(path) > 0: return path for attempt in range(retries + 1): try: resp = requests.get(url, timeout=timeout) if resp.status_code == 200 and len(resp.content) > 0: with open(path, "wb") as f: f.write(resp.content) return path except Exception: if attempt == retries: return None return None def download_card_images_batch(urls, save_dir, workers=8): """批量下载卡牌图,返回 {url: local_path or None}""" from concurrent.futures import ThreadPoolExecutor, as_completed results = {} with ThreadPoolExecutor(max_workers=workers) as pool: futures = {pool.submit(download_card_image, url, save_dir): url for url in urls} for fut in as_completed(futures): url = futures[fut] try: results[url] = fut.result() except Exception: results[url] = None return results def load_image_rgb(path): """读取图片为 RGB numpy 数组""" if path is None or not os.path.exists(path): return None img = cv2.imread(path) if img is None: return None return cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # ============ 交易图获取(MinIO / 本地) ============ def get_minio_client(minio_config): """创建 MinIO 客户端""" from minio import Minio return Minio( minio_config["endpoint"], access_key=minio_config["access_key"], secret_key=minio_config["secret_key"], secure=minio_config.get("secure", False), ) def download_transaction_image_minio(image_uri, save_dir, minio_config, bucket=None): """ 从 MinIO 下载交易图。 image_uri 形如 'ebay102/2026-03-25-11-2/xxx.jpg' bucket 为 None 时,取 image_uri 第一段作为 bucket 名 Returns: 本地路径;失败返回 None """ if not minio_config.get("access_key"): return None # 未配置账号密码 parts = image_uri.split("/", 1) if bucket is None: bkt, obj = parts[0], parts[1] if len(parts) > 1 else "" else: bkt, obj = bucket, image_uri fname = _url_to_filename(image_uri) path = os.path.join(save_dir, fname) if os.path.exists(path) and os.path.getsize(path) > 0: return path try: client = get_minio_client(minio_config) client.fget_object(bkt, obj, path) return path if os.path.exists(path) else None except Exception: return None def get_transaction_image_local(image_uri, local_root): """ 从本地目录读取交易图(若图片已下载到本地)。 local_root: 本地根目录,image_uri 拼接其后 Returns: 本地路径或 None """ path = os.path.join(local_root, image_uri) return path if os.path.exists(path) else None