illuminati360's picture
v2
40d0e50
Raw History Blame Contribute Delete
5.45 kB
import os
import gradio as gr
import duckdb
import requests
import threading
import time
import json
from datetime import datetime
DATASET_REPO = "gmongaras/Imagenet21K"
HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
# 全局状态文件
STATUS_FILE = "extraction_status.json"
OUTPUT_FILE = "ids.parquet"
def get_parquet_urls():
"""获取所有 parquet 文件 URL"""
api_url = f"https://huggingface.co/api/datasets/{DATASET_REPO}/parquet/default/train"
headers = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
data = response.json()
urls = []
items = data if isinstance(data, list) else data.get("parquet_files", [])
for item in items:
if isinstance(item, str):
urls.append(item)
else:
url = item.get("url")
if url:
urls.append(url)
return urls
def save_status(status, message, progress=0.0, error=None):
"""保存状态到文件"""
status_data = {
"status": status, # "running", "completed", "error", "idle"
"message": message,
"progress": progress,
"timestamp": datetime.now().isoformat(),
"error": error
}
with open(STATUS_FILE, 'w') as f:
json.dump(status_data, f)
def load_status():
"""从文件加载状态"""
if os.path.exists(STATUS_FILE):
try:
with open(STATUS_FILE, 'r') as f:
return json.load(f)
except:
pass
return {
"status": "idle",
"message": "没有运行中的任务",
"progress": 0.0,
"timestamp": "",
"error": None
}
def build_ids_background():
"""后台运行 DuckDB 提取任务"""
try:
save_status("running", "获取 Parquet 文件列表...", 0.0)
urls = get_parquet_urls()
save_status("running", f"找到 {len(urls)} 个文件,开始提取...", 0.1)
con = duckdb.connect()
con.execute("INSTALL httpfs; LOAD httpfs;")
# 设置认证和优化
if HF_TOKEN:
con.execute(f"SET httpfs_custom_header='Authorization: Bearer {HF_TOKEN}';")
con.execute("SET http_keep_alive=true;")
con.execute("SET enable_object_cache=true;")
con.execute("SET threads=4;")
save_status("running", "执行 SQL 查询...", 0.2)
# 构建文件列表
files_literal = ",".join([f"'{url}'" for url in urls])
# 一次性查询所有文件的 id 列
query = f"""
COPY (
SELECT id
FROM parquet_scan([{files_literal}])
) TO '{OUTPUT_FILE}' (FORMAT 'parquet', COMPRESSION 'zstd');
"""
save_status("running", "正在执行大规模查询...", 0.5)
con.execute(query)
# 完成
file_size = os.path.getsize(OUTPUT_FILE) / 1024 / 1024
save_status("completed", f"提取完成!文件大小: {file_size:.1f} MB", 1.0)
except Exception as e:
save_status("error", "提取失败", 0.0, str(e))
def start_extraction():
"""启动后台提取任务"""
status = load_status()
if status["status"] == "running":
return "任务已在运行中..."
# 删除旧的输出文件
if os.path.exists(OUTPUT_FILE):
os.remove(OUTPUT_FILE)
# 启动后台线程
thread = threading.Thread(target=build_ids_background, daemon=True)
thread.start()
return "后台任务已启动!刷新页面不会中断任务。"
def check_status():
"""检查当前状态"""
status = load_status()
progress_text = f"{status['progress']*100:.1f}%" if status['progress'] > 0 else ""
timestamp = status.get('timestamp', '')
if status['status'] == 'completed':
if os.path.exists(OUTPUT_FILE):
return f"✅ {status['message']}\n时间: {timestamp}\n进度: {progress_text}", OUTPUT_FILE
else:
return f"⚠️ 任务完成但文件不存在\n时间: {timestamp}", None
elif status['status'] == 'error':
error_msg = status.get('error', '未知错误')
return f"❌ {status['message']}\n错误: {error_msg}\n时间: {timestamp}", None
elif status['status'] == 'running':
return f"🔄 {status['message']}\n进度: {progress_text}\n时间: {timestamp}", None
else:
return status['message'], None
with gr.Blocks() as demo:
gr.Markdown("# ImageNet21K ID 提取器 (后台运行)\n✨ 支持浏览器断线重连,任务在后台持续运行")
with gr.Row():
start_btn = gr.Button("开始提取", variant="primary")
refresh_btn = gr.Button("刷新状态", variant="secondary")
with gr.Row():
status_text = gr.Textbox(label="任务状态", lines=5)
download_file = gr.File(label="下载文件")
# 自动刷新状态 (每10秒)
auto_refresh = gr.Textbox(visible=False)
start_btn.click(start_extraction, outputs=[status_text])
refresh_btn.click(check_status, outputs=[status_text, download_file])
# 页面加载时检查状态
demo.load(check_status, outputs=[status_text, download_file])
demo.queue()
if __name__ == "__main__":
demo.launch()