/** * 媒体同步脚本:将 exercises-dataset 的 images/ 与 videos/ 下载到 backend/media/, * 使后端 /media 静态服务可直接提供缩略图与动画 GIF。 * * 用法: node scripts/sync-media.mjs * 可选环境变量: * MEDIA_CONCURRENCY=8 并发下载数 * MEDIA_ONLY=gif|image 仅下载某类(gif / image) */ import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DATA_FILE = path.join(__dirname, '..', 'src', 'exercises', 'data', 'exercises.json'); const MEDIA_DIR = path.join(__dirname, '..', 'media'); const RAW_BASE = 'https://raw.githubusercontent.com/hasaneyldrm/exercises-dataset/main/'; const CONCURRENCY = parseInt(process.env.MEDIA_CONCURRENCY || '8', 10); const ONLY = process.env.MEDIA_ONLY || 'all'; async function main() { if (!fs.existsSync(DATA_FILE)) { console.error('[sync-media] 未找到数据集,请先运行 npm run build 或手动放置 exercises.json'); process.exit(1); } const raw = JSON.parse(fs.readFileSync(DATA_FILE, 'utf-8')); const paths = new Set(); for (const e of raw) { if (e.image && (ONLY === 'all' || ONLY === 'image')) paths.add(e.image); if (e.gif_url && (ONLY === 'all' || ONLY === 'gif')) paths.add(e.gif_url); } fs.mkdirSync(MEDIA_DIR, { recursive: true }); const list = Array.from(paths); console.log(`[sync-media] 待下载媒体 ${list.length} 个,并发 ${CONCURRENCY}`); let done = 0; let skipped = 0; let failed = 0; const queue = [...list]; async function worker() { while (queue.length) { const p = queue.shift(); const dest = path.join(MEDIA_DIR, p); if (fs.existsSync(dest) && fs.statSync(dest).size > 0) { skipped++; continue; } try { const res = await fetch(RAW_BASE + p); if (!res.ok) throw new Error(`HTTP ${res.status}`); const buf = Buffer.from(await res.arrayBuffer()); fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, buf); done++; } catch (err) { failed++; console.error(` 失败 ${p}: ${err.message}`); } if ((done + skipped + failed) % 50 === 0) { console.log(` 进度 ${done + skipped + failed}/${list.length}`); } } } await Promise.all(Array.from({ length: CONCURRENCY }, () => worker())); console.log(`[sync-media] 完成:新增 ${done},已存在 ${skipped},失败 ${failed}`); } main().catch((e) => { console.error('[sync-media] 错误:', e.message); process.exit(1); });