diff --git a/training-videos/.gitignore b/training-videos/.gitignore new file mode 100644 index 000000000..95f02ccbc --- /dev/null +++ b/training-videos/.gitignore @@ -0,0 +1,5 @@ +node_modules +out +__pycache__ +work +.venv diff --git a/training-videos/README.md b/training-videos/README.md new file mode 100644 index 000000000..ae8e79988 --- /dev/null +++ b/training-videos/README.md @@ -0,0 +1,83 @@ +# training-videos + +AI 跟练的素材流水线:教练“小甄”动作帧(AI 生成连续动作设定稿 → 切帧对齐 → 导出到小程序)、Remotion 示范视频、程序合成的背景音乐。 + +小程序侧代码在 `TUICallKit-Vue3/training/`(页面 `pages/pose-hub`、`pages/pose-coach`,逻辑在 `pose/`)。 + +## 教练动作帧 + +每一帧都是 AI 直接画的原图,不做合成补帧(光流、骨骼变形合成的中间帧会在四肢边缘留下残影)。 +流畅度来自三点: + +- **一个动作画在一张设定稿里**:同一次渲染,前后帧的脸、衣服、光照完全一致,播放时不会“闪” +- **帧密**:深蹲 24、侧平举 24、高抬腿每侧 13、开合跳每侧 10(动作过程约每秒 20 帧以上)。先按实测幅度挑掉倒退和重复的帧, + 再用 RIFE 在相邻两帧之间插中间帧(有残影的自动跳过),最后按幅度均匀挑出目标帧数 +- **按幅度播放**:`coachSequences.ts` 记录每帧实测的动作幅度,播放器按幅度查帧,帧间距不均匀时速度仍然均匀 +- **锁脸**(`coach_facelock.py`):每帧的五官都是单独画的,连续播放时脸会“一闪一闪”;切帧后所有帧统一换成第一帧的脸, + 并按头部对齐整帧(头不左右晃),相邻帧头部的像素差从 7~12 降到 2~4 + +左右交替的动作(开合跳、高抬腿)只画左侧,右侧由镜像换边生成:身体水平翻转,头部(脸 + 马尾)保留原图方向, +左右两侧出自同一次渲染,换边时不会跳。 + +| 步骤 | 命令 | 产物 | +|---|---|---| +| 1. 生成设定稿 | `npm run coach:sheets`(可只生成部分:`-- squat jack`) | `work/sheets/<动作>.png`(透明背景,约 1672×941) | +| 2. 采用 | `npm run coach:adopt -- <动作>` | `assets/sheets/<动作>.webp`(无损,入库) | +| 3. 切帧对齐 | `npm run coach:frames`(可只处理部分:`-- jack`) | `work/frames/master/*.png`、`work/frames/sequences.json`、`work/preview/contact_*.jpg`(检查图) | +| 4. 导出 | `npm run coach:export` | 小程序帧 `TUICallKit-Vue3/training/static/coach/*.png`(600px 轻微锐化,约 1.6MB)、COS 高清帧 `out/coach/*.webp`、视频帧 `public/coach/hd/*.webp`、帧清单 `TUICallKit-Vue3/training/pose/coachSequences.ts` | + +- 依赖:本机 `codex` CLI(已登录、开启 `image_generation`);`cwebp`;Python 依赖见 `scripts/requirements.txt` + (`python3 -m venv .venv && .venv/bin/pip install -r scripts/requirements.txt`,之后先 `source .venv/bin/activate` 再跑 npm 命令) +- 模型:关键点用 torchvision Keypoint R-CNN,超分用 Real-ESRGAN x4plus,插帧用 rife-ncnn-vulkan(v4.6),首次运行自动下载 +- 包体:训练分包上限 2MB,教练帧约占 1.6MB;加帧或提高导出尺寸前先算预算(`coach_frames.py` 的 `frames`、`coach_export.py` 的 `MINI_SIZE`) +- 角色参考图 `assets/coach-reference.png`;提示词在 `scripts/prompts/<动作>.txt` +- 姿势参考:深蹲、高抬腿用起止关键姿势 `assets/sheets/refs/*_keys.jpg`;侧平举、开合跳用 `scripts/coach_pose_guide.py` + 程序画的逐帧人偶(被拍者左侧肢体橙色、右侧蓝色)。只靠文字描述时,模型常把开头几帧画得跳变、左右脚画反 +- Codex 只能出约 1.5MP 的图,人物 300~450px 高:切帧时先 4 倍超分再缩到主帧尺寸(站姿身高 720px) +- 设定稿里手臂偶尔碰到隔壁或上一行的人物,切帧会按格子拆开;脚踝以下出现的肤色像素(别人的指尖)会被去掉 +- 生成结果有随机性:先看 `work/preview/contact_*.jpg`,不满意就重新生成那一张;`coach_frames.py` 的 `MOVES` 可指定取哪些人物 +- 手势帧(idle / wave / cheer / thumbs)和错误示范帧(wrong_*)不随动作帧重新生成,导出时沿用 `public/coach/hd` 里已有的 +- 没有 `work/` 时,可用 `python3 scripts/coach_export.py --from-hd` 从高清帧重新导出小程序帧 + +## 高清帧(COS) + +小程序分包上限 2MB,包里的教练帧只能做到 600px;手机上教练显示得大,清晰度靠 COS 上的高清帧: + +1. `npm run coach:export` 后,把 `out/coach/` 整个目录(约 92 张 WebP、3.2MB)上传到 COS,公有读, + 例如 `https://.cos..myqcloud.com/training/coach/` +2. 把这个目录地址(末尾不带斜杠)填到 `TUICallKit-Vue3/training/pose/exercises.ts` 的 `COACH_CDN_BASE` + +- 高清帧是 1000px 画布、只裁出人物所在区域(整张画布解码后 4MB/帧,一个动作几十帧手机吃不消) +- 播放时先用包内帧,高清帧在后台全部加载完再无缝切过去;没配置、网络差或任何一张加载失败时一直用包内帧 +- 地址带内容版本号(`coachSequences.ts` 的 `COACH_HD.version`),重新导出后覆盖上传同名文件即可,缓存会自动刷新 +- 开发者工具里 `COACH_CDN_BASE` 留空时读本机 `npm run serve`(`/coach/*.webp`) + +## 背景音乐 + +跟练时循环播放的舒缓背景音乐由 `npm run bgm`(`scripts/bgm_synth.py`,依赖 numpy、ffmpeg)用程序合成: +旋律、和声、音色全部是代码生成的原创内容,不含任何采样,没有版权问题。 + +1. 生成 `out/music/calm.mp3`(约 1 分 47 秒、1.6MB,128kbps),上传到 COS,例如 `.../yundong/music/calm.mp3`,公有读 +2. 地址填到 `TUICallKit-Vue3/training/pose/exercises.ts` 的 `BGM_URL`;以后换音乐时换个文件名,避免缓存 + +- 编曲:D 大调、72 拍/分,铺底长音 + 电钢琴分解和弦 + 颤音琴旋律 + 低音,32 小节首尾相接。 + 混响、延迟按循环计算,结尾的余音绕回开头;循环点落在音乐最轻的地方,播放器循环时有几十毫秒空隙也听不出来 +- 固定随机种子,重复生成结果完全一致;想换风格改脚本里的和弦(`CHORDS` / `PROGRESSION`)、节奏型(`SPARSE` 等)、旋律(`MELODY`) +- `work/music/spectrogram.png` 是频谱图,生成时会打印响度、频谱重心、循环点前后的音量,用来检查段落结构和高低频平衡 +- 小程序里的播放规则:倒计时开始淡入,暂停 / 结束淡出,切到后台立即停;教练说口令、纠错时自动压低,报数不压; + 右上角 ♪ 可以关掉(记在本地);地址打不开时不显示开关、不影响训练 +- 开发者工具里 `BGM_URL` 留空时读本机 `npm run serve`(`/music/calm.mp3`) + +## 示范视频 + +```bash +npm install +npm run studio # 预览 +npm run render # 渲染 out/{squat,march,jack,raise}.mp4,约 1.5MB/段 +npm run serve # 开发者工具联调:http://127.0.0.1:5179/squat.mp4 +``` + +上线前把 `out/*.mp4` 上传到 COS,填入 `TUICallKit-Vue3/training/pose/exercises.ts` 对应动作的 `videoUrl`; +未填写时开发环境自动使用本机 `npm run serve`,正式环境隐藏“完整示范”按钮。 + +`coach-showcase` 合成(`npx remotion render src/index.ts coach-showcase out/coach-showcase.mp4`)把四个动作并排循环,用于检查动作是否连贯。 diff --git a/training-videos/assets/coach-reference.png b/training-videos/assets/coach-reference.png new file mode 100644 index 000000000..6a8795731 Binary files /dev/null and b/training-videos/assets/coach-reference.png differ diff --git a/training-videos/assets/sheets/jack.webp b/training-videos/assets/sheets/jack.webp new file mode 100644 index 000000000..be3291ea6 Binary files /dev/null and b/training-videos/assets/sheets/jack.webp differ diff --git a/training-videos/assets/sheets/march.webp b/training-videos/assets/sheets/march.webp new file mode 100644 index 000000000..1cf5a10df Binary files /dev/null and b/training-videos/assets/sheets/march.webp differ diff --git a/training-videos/assets/sheets/raise.webp b/training-videos/assets/sheets/raise.webp new file mode 100644 index 000000000..82fe3097c Binary files /dev/null and b/training-videos/assets/sheets/raise.webp differ diff --git a/training-videos/assets/sheets/refs/march_keys.jpg b/training-videos/assets/sheets/refs/march_keys.jpg new file mode 100644 index 000000000..630327530 Binary files /dev/null and b/training-videos/assets/sheets/refs/march_keys.jpg differ diff --git a/training-videos/assets/sheets/refs/squat_keys.jpg b/training-videos/assets/sheets/refs/squat_keys.jpg new file mode 100644 index 000000000..dcef605ed Binary files /dev/null and b/training-videos/assets/sheets/refs/squat_keys.jpg differ diff --git a/training-videos/assets/sheets/squat.webp b/training-videos/assets/sheets/squat.webp new file mode 100644 index 000000000..6f8036704 Binary files /dev/null and b/training-videos/assets/sheets/squat.webp differ diff --git a/training-videos/package-lock.json b/training-videos/package-lock.json new file mode 100644 index 000000000..057c911fb --- /dev/null +++ b/training-videos/package-lock.json @@ -0,0 +1,3880 @@ +{ + "name": "training-videos", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "training-videos", + "dependencies": { + "@remotion/cli": "4.0.529", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "remotion": "4.0.529" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "typescript": "^5.6.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.1", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mediabunny/aac-encoder": { + "version": "1.56.1", + "resolved": "https://registry.npmmirror.com/@mediabunny/aac-encoder/-/aac-encoder-1.56.1.tgz", + "integrity": "sha512-a+OY/BctP7zC4G/prHikooOVaoWXMD4+bUyEwHg2dtQiQdIwC6TDJdVYJu5dfrdSNOiAFkv1HKcYEeROdwMezg==", + "license": "MPL-2.0", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@mediabunny/flac-encoder": { + "version": "1.56.1", + "resolved": "https://registry.npmmirror.com/@mediabunny/flac-encoder/-/flac-encoder-1.56.1.tgz", + "integrity": "sha512-zbmorMVlF1izwtlz5mWnu7A1rJLM+V26I0pEFr9Td1EDTwE5uAGRSW+/UZlnjwj1t00Woe3Zonfh9kb7lRRBqg==", + "license": "MPL-2.0", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@mediabunny/mp3-encoder": { + "version": "1.56.1", + "resolved": "https://registry.npmmirror.com/@mediabunny/mp3-encoder/-/mp3-encoder-1.56.1.tgz", + "integrity": "sha512-p66ovEq6mZt3ejje45NOju84hIJI4v8rHfsmc1D2J34ziV0USE8AdJBzmiwqxl6nZkIAQLNB19HzbVRcQfc51g==", + "license": "MPL-2.0", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@module-federation/error-codes": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@module-federation/error-codes/-/error-codes-0.22.0.tgz", + "integrity": "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==", + "license": "MIT" + }, + "node_modules/@module-federation/runtime": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@module-federation/runtime/-/runtime-0.22.0.tgz", + "integrity": "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==", + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.22.0", + "@module-federation/runtime-core": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@module-federation/runtime-core/-/runtime-core-0.22.0.tgz", + "integrity": "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==", + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@module-federation/runtime-tools/-/runtime-tools-0.22.0.tgz", + "integrity": "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==", + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.22.0", + "@module-federation/webpack-bundler-runtime": "0.22.0" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@module-federation/sdk/-/sdk-0.22.0.tgz", + "integrity": "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==", + "license": "MIT" + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.22.0", + "resolved": "https://registry.npmmirror.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.22.0.tgz", + "integrity": "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==", + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@remotion/bundler": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/bundler/-/bundler-4.0.529.tgz", + "integrity": "sha512-yrOoPWjwYz+E6B4uRub2L08VYZgo37T9fRaFqhtenrBFMVmR6JhftpXHyizOpQWRbeEuk1KLLo+M2duLhbi2qw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@remotion/media-parser": "4.0.529", + "@remotion/studio": "4.0.529", + "@remotion/studio-shared": "4.0.529", + "@remotion/timeline-utils": "4.0.529", + "@rspack/core": "1.7.11", + "@rspack/plugin-react-refresh": "1.6.1", + "css-loader": "7.1.4", + "esbuild": "0.28.1", + "react-refresh": "0.18.0", + "remotion": "4.0.529", + "style-loader": "4.0.0", + "webpack": "5.105.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/canvas": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/canvas/-/canvas-4.0.529.tgz", + "integrity": "sha512-zemhIIfjf+4jxwH20n+HDSEE/DxbO8HvMGdD+moozbk75E868uZWSSKNHY9EaFVi3i7hwkSv03XEktsRFLQl9A==", + "license": "Remotion License", + "dependencies": { + "@remotion/player": "4.0.529", + "remotion": "4.0.529" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/captions": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/captions/-/captions-4.0.529.tgz", + "integrity": "sha512-zXHY/FCqKqxOid60dDUVYWV/yRsIg+7F6/Qs4OMsouNqL6fC6jKwjA/+lsbimdxRUv/VUeaVVyNEufXfenQGNA==", + "license": "MIT" + }, + "node_modules/@remotion/cli": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/cli/-/cli-4.0.529.tgz", + "integrity": "sha512-1ONdF3q0adtw8FXW4CXeaSbzYV0vXIzJEm+b9mMrTDD9iyF+AKaLMzMUNespJaAvFxSxy/HV6hq17LS4UMfmiQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@babel/parser": "7.24.1", + "@remotion/bundler": "4.0.529", + "@remotion/media-utils": "4.0.529", + "@remotion/player": "4.0.529", + "@remotion/renderer": "4.0.529", + "@remotion/studio": "4.0.529", + "@remotion/studio-server": "4.0.529", + "@remotion/studio-shared": "4.0.529", + "dotenv": "17.3.1", + "minimist": "1.2.6", + "prompts": "2.4.2", + "remotion": "4.0.529", + "semver": "7.5.3" + }, + "bin": { + "remotion": "remotion-cli.js", + "remotionb": "remotionb-cli.js", + "remotiond": "remotiond-cli.js" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/codemods": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/codemods/-/codemods-4.0.529.tgz", + "integrity": "sha512-OfzvplLep4kpnxjZfnAPDTMWl+SDaudvrTpWizG7py58enZDpygoC3GD5TxqVcHi3KYja68k9vnJz/+DaAiJzw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "7.24.1", + "@babel/types": "7.24.0", + "@remotion/studio-protocol": "4.0.529", + "@remotion/studio-shared": "4.0.529", + "ast-types": "0.16.1", + "recast": "0.23.21", + "remotion": "4.0.529" + } + }, + "node_modules/@remotion/compositor-darwin-arm64": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.529.tgz", + "integrity": "sha512-uzCs1izkHlbQGvASFvnSSXodh0dfMIqaCwXX/4Zx6o+ML8MIiYfvoMXMH22bFAU6T5HYFKREhO+PKc/iDZKe4A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@remotion/compositor-darwin-x64": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.529.tgz", + "integrity": "sha512-5nPifOtkadthsrJTG4YGUR22HrAVML77zYoFKRWQGHboLcCik7UPZVy/X7yRu6HlGYuFULe0N7MKWude8jo2GA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@remotion/compositor-linux-arm64-gnu": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.529.tgz", + "integrity": "sha512-hb1gOJPGiP588tTR2pfif6xkSvYOXh3qW2FHQOIRki97kj3EyANj/+0+naVroJhrpFpE6lDHz8BMDdd8Sc6MKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-linux-arm64-musl": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.529.tgz", + "integrity": "sha512-ZmGciox6hDUDIwiw2bq9KBtA6dK/y0ZNdsYpf/06rSZJMbWM3HhhLG0k+uBb/ELhQt8ZtuFBx8neN8qq5zi5/A==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-linux-x64-gnu": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.529.tgz", + "integrity": "sha512-MSsX7ut2AOrTsgxCNPR9IulNkiMnZ4waQcUd43l8yePlsirTHVvOseB7fJtOFHDmjYJWYbbm3IcfaUWQJa68Zw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-linux-x64-musl": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.529.tgz", + "integrity": "sha512-Jada9IFuw4qY9YahXHVOgQy5tSRRLfTfaj3E5Xda7xJokju/hWUkBjUaGl6dtJmH+S+h19f5Tu2UH2J9M2v1gg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-win32-x64-msvc": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.529.tgz", + "integrity": "sha512-kyg7sG3bRdsKlcwDQ1yNMwDkAKpwlTtTrwbVWKcmii8LvHdMEDGPuteuQn6ULxwIAFqyzNkNAxhKrIjj9r8hZg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@remotion/licensing": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/licensing/-/licensing-4.0.529.tgz", + "integrity": "sha512-+hVV68ouCdjeNGKQOdDueH1OZej5biXGXLr9ADdiJr7MEl5l9mWPHa2Z9z45TwBHihMe5wKOQ5fmXHc+TBOjBg==", + "license": "MIT" + }, + "node_modules/@remotion/media": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/media/-/media-4.0.529.tgz", + "integrity": "sha512-ritB7TVRR3ZvYLNVCPwgZ33riUNn/B7IasroxLFXarMLZbz/9tHBbznO02Dth2ldkt643DTILUO/cBrnTtVWKg==", + "dependencies": { + "mediabunny": "1.56.1", + "remotion": "4.0.529", + "zod": "4.5.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/media-parser": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/media-parser/-/media-parser-4.0.529.tgz", + "integrity": "sha512-HGIm6dRtNRmGZbFZUy2/3EIWI0LW11l46qlAQs5fqQ6h24Cp+HqAfzNE05J75Hbv/2JYD+e1f5EPU/WKqykzzA==", + "license": "Remotion License https://remotion.dev/license" + }, + "node_modules/@remotion/media-utils": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/media-utils/-/media-utils-4.0.529.tgz", + "integrity": "sha512-0tYUgdRhKFSOsmGdBPFJc0TGDOz5ovyl0h984+azm9Y18w2OQwy/86Y/Vy+sB9nLRmoqn/wlaPuyH5do6iVCQw==", + "license": "MIT", + "dependencies": { + "mediabunny": "1.56.1", + "remotion": "4.0.529" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/paths": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/paths/-/paths-4.0.529.tgz", + "integrity": "sha512-kTm3lehZkjpqLi+e1rdgsMoRulDVUTkZMOUFPu9tCZDDh+gVFqQcsuOnhYZlgxnSun8f8zvy9aP2khS3Oooq3Q==", + "license": "MIT", + "dependencies": { + "remotion": "4.0.529" + } + }, + "node_modules/@remotion/player": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/player/-/player-4.0.529.tgz", + "integrity": "sha512-unUylvuwwByzIitVea5iQsBlFNqbZRElvoYVxXzeGa0jpiQRty9fYVxt3wQ97LkPEH1J1BmkSITLBWw+y/GF3w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "remotion": "4.0.529" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/renderer": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/renderer/-/renderer-4.0.529.tgz", + "integrity": "sha512-2/jo+awVY0J6d0ZDanmIVa1ZvVurDitQwjq/iFNGHRTHNAvtu1jIuY1l6OXFW0FG49KU7/2/VdOCB6+wqbF9tQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@remotion/licensing": "4.0.529", + "@remotion/streaming": "4.0.529", + "execa": "5.1.1", + "remotion": "4.0.529", + "source-map": "0.8.0", + "ws": "8.21.0" + }, + "optionalDependencies": { + "@remotion/compositor-darwin-arm64": "4.0.529", + "@remotion/compositor-darwin-x64": "4.0.529", + "@remotion/compositor-linux-arm64-gnu": "4.0.529", + "@remotion/compositor-linux-arm64-musl": "4.0.529", + "@remotion/compositor-linux-x64-gnu": "4.0.529", + "@remotion/compositor-linux-x64-musl": "4.0.529", + "@remotion/compositor-win32-x64-msvc": "4.0.529" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/streaming": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/streaming/-/streaming-4.0.529.tgz", + "integrity": "sha512-jGY65mgj1o6giB3vBmcCkRE6xu1oxVks5ogpXje1ISj3bUaptT+81flCXS8K60bhCs7lwLk1jXETsmFZLT3tsA==", + "license": "MIT" + }, + "node_modules/@remotion/studio": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/studio/-/studio-4.0.529.tgz", + "integrity": "sha512-O+uL9nU8aM3xtPoTR7aFjuwipifwpAWi+RYKaWbL2/+G9bHUE+RHmQnp+3Y9az5VgXE5sR6Sh5tXSnGqPZhK2g==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@remotion/canvas": "4.0.529", + "@remotion/captions": "4.0.529", + "@remotion/codemods": "4.0.529", + "@remotion/media": "4.0.529", + "@remotion/media-utils": "4.0.529", + "@remotion/paths": "4.0.529", + "@remotion/player": "4.0.529", + "@remotion/renderer": "4.0.529", + "@remotion/studio-protocol": "4.0.529", + "@remotion/studio-shared": "4.0.529", + "@remotion/timeline-utils": "4.0.529", + "@remotion/web-renderer": "4.0.529", + "@remotion/zod-types": "4.0.529", + "@tanstack/react-virtual": "3.14.9", + "mediabunny": "1.56.1", + "memfs": "3.4.3", + "open": "8.4.2", + "prismjs": "1.30.0", + "remotion": "4.0.529", + "semver": "7.5.3", + "zod": "4.5.4" + }, + "peerDependencies": { + "@remotion/video-matting": "4.0.529", + "@remotion/whisper-webgpu": "4.0.529", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@remotion/video-matting": { + "optional": true + }, + "@remotion/whisper-webgpu": { + "optional": true + } + } + }, + "node_modules/@remotion/studio-protocol": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/studio-protocol/-/studio-protocol-4.0.529.tgz", + "integrity": "sha512-Sndjwx2xcPRD1d2YutpzALFP6sR0t1CR9VVQXopqmgjei7Ag6HlKKw8RKT8CXd63GdiNYm3mIBsw4MMWGyiWCQ==", + "license": "Remotion License", + "dependencies": { + "zod": "4.5.4" + } + }, + "node_modules/@remotion/studio-server": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/studio-server/-/studio-server-4.0.529.tgz", + "integrity": "sha512-7il0R2ynvtd2omdCXsn3ze4XSEplHp/g9l/1TvRaK8OTgeRwHE6Kf0ko0WqsE55nzEjsOxkOo4MALA7K2p/Q7g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "7.24.1", + "@babel/types": "7.24.0", + "@remotion/bundler": "4.0.529", + "@remotion/codemods": "4.0.529", + "@remotion/renderer": "4.0.529", + "@remotion/studio-protocol": "4.0.529", + "@remotion/studio-shared": "4.0.529", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "kiwi-schema": "0.5.0", + "memfs": "3.4.3", + "open": "8.4.2", + "recast": "0.23.21", + "remotion": "4.0.529", + "semver": "7.5.3", + "zod": "4.5.4" + } + }, + "node_modules/@remotion/studio-shared": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/studio-shared/-/studio-shared-4.0.529.tgz", + "integrity": "sha512-WQoNqqmTeho932NEIzhL5yH7UOeDjSpI3Ek38g9LaDx+wFZ6jVmBYaaYqjMlAHWkmom/mRl8lbMDOxyLz/7/Jg==", + "license": "MIT", + "dependencies": { + "remotion": "4.0.529" + } + }, + "node_modules/@remotion/timeline-utils": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/timeline-utils/-/timeline-utils-4.0.529.tgz", + "integrity": "sha512-nw+H4/Q/gzxXbKU/XD8Sw9L5bpuccPS1c/CtpgZvyeEAGzlsgYPsbHInTAa++oIkasZgVK1X59qWjj5GXabiig==", + "license": "MIT", + "dependencies": { + "mediabunny": "1.56.1", + "remotion": "4.0.529" + } + }, + "node_modules/@remotion/web-renderer": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/web-renderer/-/web-renderer-4.0.529.tgz", + "integrity": "sha512-+QP9k2C8PlvBfA+hmJ17BP/my92q6O/TYEz8Qiqc1cuE+1hnPF1+iNemjv5ETEWEZ2FUwbxVOYqa9+2khmdTYQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@mediabunny/aac-encoder": "1.56.1", + "@mediabunny/flac-encoder": "1.56.1", + "@mediabunny/mp3-encoder": "1.56.1", + "@remotion/licensing": "4.0.529", + "mediabunny": "1.56.1", + "remotion": "4.0.529" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@remotion/zod-types": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/@remotion/zod-types/-/zod-types-4.0.529.tgz", + "integrity": "sha512-EInt82hUV5dH6QjI4hn4PhB9gFXiJH4QrJKJYxDI8Bog56nVGWkKLQE3vUQGhT5+3EUPdQnWEEm/OACutXvHFg==", + "license": "MIT", + "dependencies": { + "remotion": "4.0.529" + } + }, + "node_modules/@rspack/binding": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding/-/binding-1.7.11.tgz", + "integrity": "sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==", + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.7.11", + "@rspack/binding-darwin-x64": "1.7.11", + "@rspack/binding-linux-arm64-gnu": "1.7.11", + "@rspack/binding-linux-arm64-musl": "1.7.11", + "@rspack/binding-linux-x64-gnu": "1.7.11", + "@rspack/binding-linux-x64-musl": "1.7.11", + "@rspack/binding-wasm32-wasi": "1.7.11", + "@rspack/binding-win32-arm64-msvc": "1.7.11", + "@rspack/binding-win32-ia32-msvc": "1.7.11", + "@rspack/binding-win32-x64-msvc": "1.7.11" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.11.tgz", + "integrity": "sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.11.tgz", + "integrity": "sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.11.tgz", + "integrity": "sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.11.tgz", + "integrity": "sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.11.tgz", + "integrity": "sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.11.tgz", + "integrity": "sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.11.tgz", + "integrity": "sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "1.0.7" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.11.tgz", + "integrity": "sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.11.tgz", + "integrity": "sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.11.tgz", + "integrity": "sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/core": { + "version": "1.7.11", + "resolved": "https://registry.npmmirror.com/@rspack/core/-/core-1.7.11.tgz", + "integrity": "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==", + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.22.0", + "@rspack/binding": "1.7.11", + "@rspack/lite-tapable": "1.1.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz", + "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==", + "license": "MIT" + }, + "node_modules/@rspack/plugin-react-refresh": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz", + "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==", + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.1.4", + "html-entities": "^2.6.0" + }, + "peerDependencies": { + "react-refresh": ">=0.10.0 <1.0.0", + "webpack-hot-middleware": "2.x" + }, + "peerDependenciesMeta": { + "webpack-hot-middleware": { + "optional": true + } + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.9", + "resolved": "https://registry.npmmirror.com/@tanstack/react-virtual/-/react-virtual-3.14.9.tgz", + "integrity": "sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.7", + "resolved": "https://registry.npmmirror.com/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz", + "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/dom-mediacapture-transform": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.12.tgz", + "integrity": "sha512-d7/QsLRwF864A5mgIM/YrfiglHoYn7zgCcAoJgW404r+2DwnNr7EBbLnCWpmOMgH8y0te73L1AV6H1bmauaWFw==", + "license": "MIT", + "dependencies": { + "@types/dom-webcodecs": "*" + } + }, + "node_modules/@types/dom-webcodecs": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", + "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.3", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.26", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz", + "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.29.1", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.29.1.tgz", + "integrity": "sha512-AUdjuRyCNGUYtqpqfTmWyM4fXay8yIQhmLnvYe/THMGfT9B/34X7xQd3ifKxwyNPPpowVBjLb+64BN9Rn1mizw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.25", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.438", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001812", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001812.tgz", + "integrity": "sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.439", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.439.tgz", + "integrity": "sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==", + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.25.1", + "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", + "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "license": "Unlicense" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kiwi-schema": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/kiwi-schema/-/kiwi-schema-0.5.0.tgz", + "integrity": "sha512-X+FpfU0yTEtc6aTHS7VwbOpvQwRt70+pXXWRI5fd6CvWhe7pSVC854TVo4Zo0x5/wwcWj+/9KUlXpdcP0dY9AA==", + "license": "MIT", + "bin": { + "kiwic": "cli.js" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mediabunny": { + "version": "1.56.1", + "resolved": "https://registry.npmmirror.com/mediabunny/-/mediabunny-1.56.1.tgz", + "integrity": "sha512-DNOm0bRBmeOxJgCK/g3LxNWbxY5morTauNdlychujGmJ9L37ezVezkNoI1WtO2Xdb4WlWWJlYfIKnOEzd5phRw==", + "license": "MPL-2.0", + "workspaces": [ + ".", + "packages/*" + ], + "dependencies": { + "@types/dom-mediacapture-transform": "^0.1.11", + "@types/dom-webcodecs": "0.1.13" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + } + }, + "node_modules/memfs": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", + "license": "Unlicense", + "dependencies": { + "fs-monkey": "1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.57", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmmirror.com/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.21", + "resolved": "https://registry.npmmirror.com/recast/-/recast-0.23.21.tgz", + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remotion": { + "version": "4.0.529", + "resolved": "https://registry.npmmirror.com/remotion/-/remotion-4.0.529.tgz", + "integrity": "sha512-x0oWWwPPiC5cV6ms80USr69GmtbHc3MK5eFTwJpP0oN+emvgtvq+flovERaN29jy0wJW/ZGemSwo119iGCiH0g==", + "license": "SEE LICENSE IN LICENSE.md", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.5.0.tgz", + "integrity": "sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.8.0.tgz", + "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/svg-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.1.0.tgz", + "integrity": "sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.105.0", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.6.0.tgz", + "integrity": "sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/training-videos/package.json b/training-videos/package.json new file mode 100644 index 000000000..fc7024365 --- /dev/null +++ b/training-videos/package.json @@ -0,0 +1,26 @@ +{ + "name": "training-videos", + "private": true, + "description": "AI 跟练示范视频与封面(Remotion)", + "scripts": { + "studio": "remotion studio", + "coach:sheets": "zsh scripts/gen-sheets.sh", + "coach:adopt": "python3 scripts/coach_adopt.py", + "coach:frames": "python3 scripts/coach_frames.py", + "coach:export": "python3 scripts/coach_export.py", + "bgm": "python3 scripts/bgm_synth.py", + "render": "node scripts/render-all.mjs", + "serve": "node scripts/serve.mjs", + "render:one": "remotion render" + }, + "dependencies": { + "@remotion/cli": "4.0.529", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "remotion": "4.0.529" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "typescript": "^5.6.3" + } +} diff --git a/training-videos/public/coach/hd/cheer.webp b/training-videos/public/coach/hd/cheer.webp new file mode 100644 index 000000000..f671db183 Binary files /dev/null and b/training-videos/public/coach/hd/cheer.webp differ diff --git a/training-videos/public/coach/hd/idle.webp b/training-videos/public/coach/hd/idle.webp new file mode 100644 index 000000000..961371dcb Binary files /dev/null and b/training-videos/public/coach/hd/idle.webp differ diff --git a/training-videos/public/coach/hd/jl_00.webp b/training-videos/public/coach/hd/jl_00.webp new file mode 100644 index 000000000..5e35e9fab Binary files /dev/null and b/training-videos/public/coach/hd/jl_00.webp differ diff --git a/training-videos/public/coach/hd/jl_01.webp b/training-videos/public/coach/hd/jl_01.webp new file mode 100644 index 000000000..d43edf54a Binary files /dev/null and b/training-videos/public/coach/hd/jl_01.webp differ diff --git a/training-videos/public/coach/hd/jl_02.webp b/training-videos/public/coach/hd/jl_02.webp new file mode 100644 index 000000000..7633591aa Binary files /dev/null and b/training-videos/public/coach/hd/jl_02.webp differ diff --git a/training-videos/public/coach/hd/jl_03.webp b/training-videos/public/coach/hd/jl_03.webp new file mode 100644 index 000000000..5cdadc38d Binary files /dev/null and b/training-videos/public/coach/hd/jl_03.webp differ diff --git a/training-videos/public/coach/hd/jl_04.webp b/training-videos/public/coach/hd/jl_04.webp new file mode 100644 index 000000000..65ca1ea53 Binary files /dev/null and b/training-videos/public/coach/hd/jl_04.webp differ diff --git a/training-videos/public/coach/hd/jl_05.webp b/training-videos/public/coach/hd/jl_05.webp new file mode 100644 index 000000000..2a6652a84 Binary files /dev/null and b/training-videos/public/coach/hd/jl_05.webp differ diff --git a/training-videos/public/coach/hd/jl_06.webp b/training-videos/public/coach/hd/jl_06.webp new file mode 100644 index 000000000..5e21a7c2a Binary files /dev/null and b/training-videos/public/coach/hd/jl_06.webp differ diff --git a/training-videos/public/coach/hd/jl_07.webp b/training-videos/public/coach/hd/jl_07.webp new file mode 100644 index 000000000..a49055984 Binary files /dev/null and b/training-videos/public/coach/hd/jl_07.webp differ diff --git a/training-videos/public/coach/hd/jl_08.webp b/training-videos/public/coach/hd/jl_08.webp new file mode 100644 index 000000000..3a5df5023 Binary files /dev/null and b/training-videos/public/coach/hd/jl_08.webp differ diff --git a/training-videos/public/coach/hd/jl_09.webp b/training-videos/public/coach/hd/jl_09.webp new file mode 100644 index 000000000..839d29873 Binary files /dev/null and b/training-videos/public/coach/hd/jl_09.webp differ diff --git a/training-videos/public/coach/hd/jr_01.webp b/training-videos/public/coach/hd/jr_01.webp new file mode 100644 index 000000000..121e2dfa3 Binary files /dev/null and b/training-videos/public/coach/hd/jr_01.webp differ diff --git a/training-videos/public/coach/hd/jr_02.webp b/training-videos/public/coach/hd/jr_02.webp new file mode 100644 index 000000000..4c30f58c0 Binary files /dev/null and b/training-videos/public/coach/hd/jr_02.webp differ diff --git a/training-videos/public/coach/hd/jr_03.webp b/training-videos/public/coach/hd/jr_03.webp new file mode 100644 index 000000000..9470dff48 Binary files /dev/null and b/training-videos/public/coach/hd/jr_03.webp differ diff --git a/training-videos/public/coach/hd/jr_04.webp b/training-videos/public/coach/hd/jr_04.webp new file mode 100644 index 000000000..fc1da37ab Binary files /dev/null and b/training-videos/public/coach/hd/jr_04.webp differ diff --git a/training-videos/public/coach/hd/jr_05.webp b/training-videos/public/coach/hd/jr_05.webp new file mode 100644 index 000000000..2f93c3343 Binary files /dev/null and b/training-videos/public/coach/hd/jr_05.webp differ diff --git a/training-videos/public/coach/hd/jr_06.webp b/training-videos/public/coach/hd/jr_06.webp new file mode 100644 index 000000000..7f4036ef3 Binary files /dev/null and b/training-videos/public/coach/hd/jr_06.webp differ diff --git a/training-videos/public/coach/hd/jr_07.webp b/training-videos/public/coach/hd/jr_07.webp new file mode 100644 index 000000000..377805330 Binary files /dev/null and b/training-videos/public/coach/hd/jr_07.webp differ diff --git a/training-videos/public/coach/hd/jr_08.webp b/training-videos/public/coach/hd/jr_08.webp new file mode 100644 index 000000000..01f27ccc0 Binary files /dev/null and b/training-videos/public/coach/hd/jr_08.webp differ diff --git a/training-videos/public/coach/hd/jr_09.webp b/training-videos/public/coach/hd/jr_09.webp new file mode 100644 index 000000000..6260d6c7b Binary files /dev/null and b/training-videos/public/coach/hd/jr_09.webp differ diff --git a/training-videos/public/coach/hd/ml_00.webp b/training-videos/public/coach/hd/ml_00.webp new file mode 100644 index 000000000..26109dd7c Binary files /dev/null and b/training-videos/public/coach/hd/ml_00.webp differ diff --git a/training-videos/public/coach/hd/ml_01.webp b/training-videos/public/coach/hd/ml_01.webp new file mode 100644 index 000000000..ffe31c8de Binary files /dev/null and b/training-videos/public/coach/hd/ml_01.webp differ diff --git a/training-videos/public/coach/hd/ml_02.webp b/training-videos/public/coach/hd/ml_02.webp new file mode 100644 index 000000000..7d25afa95 Binary files /dev/null and b/training-videos/public/coach/hd/ml_02.webp differ diff --git a/training-videos/public/coach/hd/ml_03.webp b/training-videos/public/coach/hd/ml_03.webp new file mode 100644 index 000000000..baf3220a7 Binary files /dev/null and b/training-videos/public/coach/hd/ml_03.webp differ diff --git a/training-videos/public/coach/hd/ml_04.webp b/training-videos/public/coach/hd/ml_04.webp new file mode 100644 index 000000000..22175561e Binary files /dev/null and b/training-videos/public/coach/hd/ml_04.webp differ diff --git a/training-videos/public/coach/hd/ml_05.webp b/training-videos/public/coach/hd/ml_05.webp new file mode 100644 index 000000000..acfc9d527 Binary files /dev/null and b/training-videos/public/coach/hd/ml_05.webp differ diff --git a/training-videos/public/coach/hd/ml_06.webp b/training-videos/public/coach/hd/ml_06.webp new file mode 100644 index 000000000..e33b8ab5c Binary files /dev/null and b/training-videos/public/coach/hd/ml_06.webp differ diff --git a/training-videos/public/coach/hd/ml_07.webp b/training-videos/public/coach/hd/ml_07.webp new file mode 100644 index 000000000..3450631d8 Binary files /dev/null and b/training-videos/public/coach/hd/ml_07.webp differ diff --git a/training-videos/public/coach/hd/ml_08.webp b/training-videos/public/coach/hd/ml_08.webp new file mode 100644 index 000000000..69e6e6e35 Binary files /dev/null and b/training-videos/public/coach/hd/ml_08.webp differ diff --git a/training-videos/public/coach/hd/ml_09.webp b/training-videos/public/coach/hd/ml_09.webp new file mode 100644 index 000000000..09c48f0d7 Binary files /dev/null and b/training-videos/public/coach/hd/ml_09.webp differ diff --git a/training-videos/public/coach/hd/ml_10.webp b/training-videos/public/coach/hd/ml_10.webp new file mode 100644 index 000000000..02f7a80b8 Binary files /dev/null and b/training-videos/public/coach/hd/ml_10.webp differ diff --git a/training-videos/public/coach/hd/ml_11.webp b/training-videos/public/coach/hd/ml_11.webp new file mode 100644 index 000000000..43de0d832 Binary files /dev/null and b/training-videos/public/coach/hd/ml_11.webp differ diff --git a/training-videos/public/coach/hd/ml_12.webp b/training-videos/public/coach/hd/ml_12.webp new file mode 100644 index 000000000..9b39dd56b Binary files /dev/null and b/training-videos/public/coach/hd/ml_12.webp differ diff --git a/training-videos/public/coach/hd/mr_01.webp b/training-videos/public/coach/hd/mr_01.webp new file mode 100644 index 000000000..e4d9015a9 Binary files /dev/null and b/training-videos/public/coach/hd/mr_01.webp differ diff --git a/training-videos/public/coach/hd/mr_02.webp b/training-videos/public/coach/hd/mr_02.webp new file mode 100644 index 000000000..e512ecc8d Binary files /dev/null and b/training-videos/public/coach/hd/mr_02.webp differ diff --git a/training-videos/public/coach/hd/mr_03.webp b/training-videos/public/coach/hd/mr_03.webp new file mode 100644 index 000000000..b1fb0891c Binary files /dev/null and b/training-videos/public/coach/hd/mr_03.webp differ diff --git a/training-videos/public/coach/hd/mr_04.webp b/training-videos/public/coach/hd/mr_04.webp new file mode 100644 index 000000000..f1298cca6 Binary files /dev/null and b/training-videos/public/coach/hd/mr_04.webp differ diff --git a/training-videos/public/coach/hd/mr_05.webp b/training-videos/public/coach/hd/mr_05.webp new file mode 100644 index 000000000..9091546bb Binary files /dev/null and b/training-videos/public/coach/hd/mr_05.webp differ diff --git a/training-videos/public/coach/hd/mr_06.webp b/training-videos/public/coach/hd/mr_06.webp new file mode 100644 index 000000000..b40cdcf47 Binary files /dev/null and b/training-videos/public/coach/hd/mr_06.webp differ diff --git a/training-videos/public/coach/hd/mr_07.webp b/training-videos/public/coach/hd/mr_07.webp new file mode 100644 index 000000000..e27accf9d Binary files /dev/null and b/training-videos/public/coach/hd/mr_07.webp differ diff --git a/training-videos/public/coach/hd/mr_08.webp b/training-videos/public/coach/hd/mr_08.webp new file mode 100644 index 000000000..653a4c362 Binary files /dev/null and b/training-videos/public/coach/hd/mr_08.webp differ diff --git a/training-videos/public/coach/hd/mr_09.webp b/training-videos/public/coach/hd/mr_09.webp new file mode 100644 index 000000000..30fcb8450 Binary files /dev/null and b/training-videos/public/coach/hd/mr_09.webp differ diff --git a/training-videos/public/coach/hd/mr_10.webp b/training-videos/public/coach/hd/mr_10.webp new file mode 100644 index 000000000..d5fc0a11e Binary files /dev/null and b/training-videos/public/coach/hd/mr_10.webp differ diff --git a/training-videos/public/coach/hd/mr_11.webp b/training-videos/public/coach/hd/mr_11.webp new file mode 100644 index 000000000..3b458d21a Binary files /dev/null and b/training-videos/public/coach/hd/mr_11.webp differ diff --git a/training-videos/public/coach/hd/mr_12.webp b/training-videos/public/coach/hd/mr_12.webp new file mode 100644 index 000000000..e609d2bca Binary files /dev/null and b/training-videos/public/coach/hd/mr_12.webp differ diff --git a/training-videos/public/coach/hd/ra_00.webp b/training-videos/public/coach/hd/ra_00.webp new file mode 100644 index 000000000..3f95c316e Binary files /dev/null and b/training-videos/public/coach/hd/ra_00.webp differ diff --git a/training-videos/public/coach/hd/ra_01.webp b/training-videos/public/coach/hd/ra_01.webp new file mode 100644 index 000000000..ec3f0f3cd Binary files /dev/null and b/training-videos/public/coach/hd/ra_01.webp differ diff --git a/training-videos/public/coach/hd/ra_02.webp b/training-videos/public/coach/hd/ra_02.webp new file mode 100644 index 000000000..29499a277 Binary files /dev/null and b/training-videos/public/coach/hd/ra_02.webp differ diff --git a/training-videos/public/coach/hd/ra_03.webp b/training-videos/public/coach/hd/ra_03.webp new file mode 100644 index 000000000..e7d78dc11 Binary files /dev/null and b/training-videos/public/coach/hd/ra_03.webp differ diff --git a/training-videos/public/coach/hd/ra_04.webp b/training-videos/public/coach/hd/ra_04.webp new file mode 100644 index 000000000..d969bbe7f Binary files /dev/null and b/training-videos/public/coach/hd/ra_04.webp differ diff --git a/training-videos/public/coach/hd/ra_05.webp b/training-videos/public/coach/hd/ra_05.webp new file mode 100644 index 000000000..ce2778a31 Binary files /dev/null and b/training-videos/public/coach/hd/ra_05.webp differ diff --git a/training-videos/public/coach/hd/ra_06.webp b/training-videos/public/coach/hd/ra_06.webp new file mode 100644 index 000000000..b2ed3ee94 Binary files /dev/null and b/training-videos/public/coach/hd/ra_06.webp differ diff --git a/training-videos/public/coach/hd/ra_07.webp b/training-videos/public/coach/hd/ra_07.webp new file mode 100644 index 000000000..5bd2b60f6 Binary files /dev/null and b/training-videos/public/coach/hd/ra_07.webp differ diff --git a/training-videos/public/coach/hd/ra_08.webp b/training-videos/public/coach/hd/ra_08.webp new file mode 100644 index 000000000..5636050b8 Binary files /dev/null and b/training-videos/public/coach/hd/ra_08.webp differ diff --git a/training-videos/public/coach/hd/ra_09.webp b/training-videos/public/coach/hd/ra_09.webp new file mode 100644 index 000000000..9c5fb264a Binary files /dev/null and b/training-videos/public/coach/hd/ra_09.webp differ diff --git a/training-videos/public/coach/hd/ra_10.webp b/training-videos/public/coach/hd/ra_10.webp new file mode 100644 index 000000000..22735e6a5 Binary files /dev/null and b/training-videos/public/coach/hd/ra_10.webp differ diff --git a/training-videos/public/coach/hd/ra_11.webp b/training-videos/public/coach/hd/ra_11.webp new file mode 100644 index 000000000..7d0fa5a96 Binary files /dev/null and b/training-videos/public/coach/hd/ra_11.webp differ diff --git a/training-videos/public/coach/hd/ra_12.webp b/training-videos/public/coach/hd/ra_12.webp new file mode 100644 index 000000000..e65df18f4 Binary files /dev/null and b/training-videos/public/coach/hd/ra_12.webp differ diff --git a/training-videos/public/coach/hd/ra_13.webp b/training-videos/public/coach/hd/ra_13.webp new file mode 100644 index 000000000..cd8b220fa Binary files /dev/null and b/training-videos/public/coach/hd/ra_13.webp differ diff --git a/training-videos/public/coach/hd/ra_14.webp b/training-videos/public/coach/hd/ra_14.webp new file mode 100644 index 000000000..334a42b87 Binary files /dev/null and b/training-videos/public/coach/hd/ra_14.webp differ diff --git a/training-videos/public/coach/hd/ra_15.webp b/training-videos/public/coach/hd/ra_15.webp new file mode 100644 index 000000000..12b775900 Binary files /dev/null and b/training-videos/public/coach/hd/ra_15.webp differ diff --git a/training-videos/public/coach/hd/ra_16.webp b/training-videos/public/coach/hd/ra_16.webp new file mode 100644 index 000000000..25946f2ab Binary files /dev/null and b/training-videos/public/coach/hd/ra_16.webp differ diff --git a/training-videos/public/coach/hd/ra_17.webp b/training-videos/public/coach/hd/ra_17.webp new file mode 100644 index 000000000..d314e7151 Binary files /dev/null and b/training-videos/public/coach/hd/ra_17.webp differ diff --git a/training-videos/public/coach/hd/ra_18.webp b/training-videos/public/coach/hd/ra_18.webp new file mode 100644 index 000000000..320a7f274 Binary files /dev/null and b/training-videos/public/coach/hd/ra_18.webp differ diff --git a/training-videos/public/coach/hd/ra_19.webp b/training-videos/public/coach/hd/ra_19.webp new file mode 100644 index 000000000..7294378ed Binary files /dev/null and b/training-videos/public/coach/hd/ra_19.webp differ diff --git a/training-videos/public/coach/hd/ra_20.webp b/training-videos/public/coach/hd/ra_20.webp new file mode 100644 index 000000000..4d4899802 Binary files /dev/null and b/training-videos/public/coach/hd/ra_20.webp differ diff --git a/training-videos/public/coach/hd/ra_21.webp b/training-videos/public/coach/hd/ra_21.webp new file mode 100644 index 000000000..1c1e3d28e Binary files /dev/null and b/training-videos/public/coach/hd/ra_21.webp differ diff --git a/training-videos/public/coach/hd/ra_22.webp b/training-videos/public/coach/hd/ra_22.webp new file mode 100644 index 000000000..e17d2a38b Binary files /dev/null and b/training-videos/public/coach/hd/ra_22.webp differ diff --git a/training-videos/public/coach/hd/ra_23.webp b/training-videos/public/coach/hd/ra_23.webp new file mode 100644 index 000000000..afd6a560a Binary files /dev/null and b/training-videos/public/coach/hd/ra_23.webp differ diff --git a/training-videos/public/coach/hd/sq_00.webp b/training-videos/public/coach/hd/sq_00.webp new file mode 100644 index 000000000..6cfd31d5f Binary files /dev/null and b/training-videos/public/coach/hd/sq_00.webp differ diff --git a/training-videos/public/coach/hd/sq_01.webp b/training-videos/public/coach/hd/sq_01.webp new file mode 100644 index 000000000..079feed2a Binary files /dev/null and b/training-videos/public/coach/hd/sq_01.webp differ diff --git a/training-videos/public/coach/hd/sq_02.webp b/training-videos/public/coach/hd/sq_02.webp new file mode 100644 index 000000000..1af262d94 Binary files /dev/null and b/training-videos/public/coach/hd/sq_02.webp differ diff --git a/training-videos/public/coach/hd/sq_03.webp b/training-videos/public/coach/hd/sq_03.webp new file mode 100644 index 000000000..69766a8b4 Binary files /dev/null and b/training-videos/public/coach/hd/sq_03.webp differ diff --git a/training-videos/public/coach/hd/sq_04.webp b/training-videos/public/coach/hd/sq_04.webp new file mode 100644 index 000000000..87af0cfbe Binary files /dev/null and b/training-videos/public/coach/hd/sq_04.webp differ diff --git a/training-videos/public/coach/hd/sq_05.webp b/training-videos/public/coach/hd/sq_05.webp new file mode 100644 index 000000000..6003be03c Binary files /dev/null and b/training-videos/public/coach/hd/sq_05.webp differ diff --git a/training-videos/public/coach/hd/sq_06.webp b/training-videos/public/coach/hd/sq_06.webp new file mode 100644 index 000000000..eeda1afd8 Binary files /dev/null and b/training-videos/public/coach/hd/sq_06.webp differ diff --git a/training-videos/public/coach/hd/sq_07.webp b/training-videos/public/coach/hd/sq_07.webp new file mode 100644 index 000000000..a0f31df14 Binary files /dev/null and b/training-videos/public/coach/hd/sq_07.webp differ diff --git a/training-videos/public/coach/hd/sq_08.webp b/training-videos/public/coach/hd/sq_08.webp new file mode 100644 index 000000000..dc05d1ccf Binary files /dev/null and b/training-videos/public/coach/hd/sq_08.webp differ diff --git a/training-videos/public/coach/hd/sq_09.webp b/training-videos/public/coach/hd/sq_09.webp new file mode 100644 index 000000000..3dd37cc80 Binary files /dev/null and b/training-videos/public/coach/hd/sq_09.webp differ diff --git a/training-videos/public/coach/hd/sq_10.webp b/training-videos/public/coach/hd/sq_10.webp new file mode 100644 index 000000000..61e3ca6cf Binary files /dev/null and b/training-videos/public/coach/hd/sq_10.webp differ diff --git a/training-videos/public/coach/hd/sq_11.webp b/training-videos/public/coach/hd/sq_11.webp new file mode 100644 index 000000000..94efe59de Binary files /dev/null and b/training-videos/public/coach/hd/sq_11.webp differ diff --git a/training-videos/public/coach/hd/sq_12.webp b/training-videos/public/coach/hd/sq_12.webp new file mode 100644 index 000000000..1d60c79ea Binary files /dev/null and b/training-videos/public/coach/hd/sq_12.webp differ diff --git a/training-videos/public/coach/hd/sq_13.webp b/training-videos/public/coach/hd/sq_13.webp new file mode 100644 index 000000000..5d909df8b Binary files /dev/null and b/training-videos/public/coach/hd/sq_13.webp differ diff --git a/training-videos/public/coach/hd/sq_14.webp b/training-videos/public/coach/hd/sq_14.webp new file mode 100644 index 000000000..9831ad78a Binary files /dev/null and b/training-videos/public/coach/hd/sq_14.webp differ diff --git a/training-videos/public/coach/hd/sq_15.webp b/training-videos/public/coach/hd/sq_15.webp new file mode 100644 index 000000000..a15ff7cf4 Binary files /dev/null and b/training-videos/public/coach/hd/sq_15.webp differ diff --git a/training-videos/public/coach/hd/sq_16.webp b/training-videos/public/coach/hd/sq_16.webp new file mode 100644 index 000000000..3e6ec5b89 Binary files /dev/null and b/training-videos/public/coach/hd/sq_16.webp differ diff --git a/training-videos/public/coach/hd/sq_17.webp b/training-videos/public/coach/hd/sq_17.webp new file mode 100644 index 000000000..f8b2c50a3 Binary files /dev/null and b/training-videos/public/coach/hd/sq_17.webp differ diff --git a/training-videos/public/coach/hd/sq_18.webp b/training-videos/public/coach/hd/sq_18.webp new file mode 100644 index 000000000..d88b701ab Binary files /dev/null and b/training-videos/public/coach/hd/sq_18.webp differ diff --git a/training-videos/public/coach/hd/sq_19.webp b/training-videos/public/coach/hd/sq_19.webp new file mode 100644 index 000000000..3054ff191 Binary files /dev/null and b/training-videos/public/coach/hd/sq_19.webp differ diff --git a/training-videos/public/coach/hd/sq_20.webp b/training-videos/public/coach/hd/sq_20.webp new file mode 100644 index 000000000..ef37ab53c Binary files /dev/null and b/training-videos/public/coach/hd/sq_20.webp differ diff --git a/training-videos/public/coach/hd/sq_21.webp b/training-videos/public/coach/hd/sq_21.webp new file mode 100644 index 000000000..8e794d0b8 Binary files /dev/null and b/training-videos/public/coach/hd/sq_21.webp differ diff --git a/training-videos/public/coach/hd/sq_22.webp b/training-videos/public/coach/hd/sq_22.webp new file mode 100644 index 000000000..935405e78 Binary files /dev/null and b/training-videos/public/coach/hd/sq_22.webp differ diff --git a/training-videos/public/coach/hd/sq_23.webp b/training-videos/public/coach/hd/sq_23.webp new file mode 100644 index 000000000..96b9b4a47 Binary files /dev/null and b/training-videos/public/coach/hd/sq_23.webp differ diff --git a/training-videos/public/coach/hd/thumbs.webp b/training-videos/public/coach/hd/thumbs.webp new file mode 100644 index 000000000..4fcbb73f4 Binary files /dev/null and b/training-videos/public/coach/hd/thumbs.webp differ diff --git a/training-videos/public/coach/hd/wave.webp b/training-videos/public/coach/hd/wave.webp new file mode 100644 index 000000000..236cd36ad Binary files /dev/null and b/training-videos/public/coach/hd/wave.webp differ diff --git a/training-videos/public/coach/hd/wrong_jack.webp b/training-videos/public/coach/hd/wrong_jack.webp new file mode 100644 index 000000000..55c6bfbf3 Binary files /dev/null and b/training-videos/public/coach/hd/wrong_jack.webp differ diff --git a/training-videos/public/coach/hd/wrong_march.webp b/training-videos/public/coach/hd/wrong_march.webp new file mode 100644 index 000000000..5b4ffd4e6 Binary files /dev/null and b/training-videos/public/coach/hd/wrong_march.webp differ diff --git a/training-videos/public/coach/hd/wrong_raise.webp b/training-videos/public/coach/hd/wrong_raise.webp new file mode 100644 index 000000000..a5ebb6611 Binary files /dev/null and b/training-videos/public/coach/hd/wrong_raise.webp differ diff --git a/training-videos/public/coach/hd/wrong_squat.webp b/training-videos/public/coach/hd/wrong_squat.webp new file mode 100644 index 000000000..10e7a85f8 Binary files /dev/null and b/training-videos/public/coach/hd/wrong_squat.webp differ diff --git a/training-videos/remotion.config.ts b/training-videos/remotion.config.ts new file mode 100644 index 000000000..9486e2b8d --- /dev/null +++ b/training-videos/remotion.config.ts @@ -0,0 +1,7 @@ +import { Config } from '@remotion/cli/config' + +Config.setVideoImageFormat('jpeg') +Config.setOverwriteOutput(true) +// 小程序播放:H.264 + 较高压缩,控制单个视频在 2MB 左右 +Config.setCodec('h264') +Config.setCrf(28) diff --git a/training-videos/scripts/bgm_synth.py b/training-videos/scripts/bgm_synth.py new file mode 100644 index 000000000..ebd315888 --- /dev/null +++ b/training-videos/scripts/bgm_synth.py @@ -0,0 +1,348 @@ +""" +训练背景音乐:用程序合成一段舒缓的循环氛围音乐(原创,不含任何采样和版权素材) + +用法:python3 scripts/bgm_synth.py +产物:out/music/calm.mp3 上传到 COS,地址填到 TUICallKit-Vue3/training/pose/exercises.ts 的 BGM_URL + work/music/calm.wav、work/music/spectrogram.png 检查用 + +编曲:D 大调,72 拍/分,32 小节(约 1 分 47 秒),首尾相接循环播放 + A 铺底长音 + 电钢琴分解和弦(3+3+2 的节奏,比拍子慢一点,轻轻摇晃) + A' 加入颤音琴长音旋律,高八度的“光晕”铺底进来 + B 换一组和弦(低音 G→F#→E→A 下行),旋律继续 + A'' 分解和弦变密,最后两小节逐渐变稀 +混响、延迟、滤波都按首尾相接计算(循环卷积),结尾的余音绕回开头,循环处没有接缝; +循环点落在属和弦回到主和弦、音乐最轻的地方,播放器循环时即使空出几十毫秒也听不出来。 +随机数固定种子,每次生成的结果完全一样。 +""" +import subprocess +import wave +from pathlib import Path + +import numpy as np + +ROOT = Path(__file__).resolve().parents[1] +MP3 = ROOT / "out" / "music" / "calm.mp3" +WORK = ROOT / "work" / "music" + +SR = 44100 +BPM = 72 +BEAT = 60 / BPM +BARS = 32 +N = round(BARS * 4 * BEAT * SR) +LOOP_S = N / SR +SEED = 20260929 +# 整体响度(RMS);小程序里再按 0.35 左右的音量播放,比教练语音低一截 +TARGET_RMS_DB = -19.0 +PEAK_LIMIT = 10 ** (-1.0 / 20) +# 首尾各 30ms 淡入淡出:播放器循环时如果有空隙,也不会“咔哒”一声 +EDGE_FADE_S = 0.03 +MP3_BITRATE = "128k" + +rng = np.random.default_rng(SEED) + +# 和弦:铺底声部、低音、分解和弦用音(MIDI 音高,60 = 中央 C) +CHORDS = { + "Dmaj9": ([54, 57, 61, 64], 38, [66, 69, 73, 76, 78, 81]), + "Bm11": ([54, 57, 62, 64], 35, [66, 69, 71, 74, 76, 78]), + "Gmaj9": ([54, 57, 59, 62], 31, [67, 71, 74, 78, 81, 83]), + "A9sus4": ([55, 59, 62, 64], 33, [64, 69, 71, 74, 76, 79]), + "Gmaj7": ([55, 59, 62, 66], 31, [67, 71, 74, 78, 79, 83]), + "F#m7": ([54, 57, 61, 64], 30, [66, 69, 73, 76, 78, 81]), + "Em9": ([55, 59, 62, 66], 28, [64, 67, 71, 74, 78, 79]), + "A13sus4": ([55, 59, 62, 66], 33, [64, 69, 71, 74, 76, 78]), +} +# 每个和弦两小节,共 16 个 +PROGRESSION = ["Dmaj9", "Bm11", "Gmaj9", "A9sus4"] * 2 + ["Gmaj7", "F#m7", "Em9", "A13sus4"] + ["Dmaj9", "Bm11", "Gmaj9", "A9sus4"] +SECTIONS = ["A"] * 4 + ["A1"] * 4 + ["B"] * 4 + ["A2"] * 4 +# 铺底的明亮程度(滤波开合),段落之间有起伏 +BRIGHTNESS = {"A": 0.25, "A1": 0.35, "B": 0.5, "A2": 0.4} + +# 分解和弦:(两小节内第几个八分音符, 用第几个和弦音, 力度) +SPARSE = [(0, 0, 0.62), (3, 2, 0.46), (6, 4, 0.52), (8, 3, 0.56), (11, 1, 0.42), (14, 2, 0.48)] +FALL = [(0, 5, 0.56), (2, 4, 0.42), (3, 2, 0.46), (6, 3, 0.5), (8, 4, 0.54), (10, 3, 0.4), (11, 1, 0.44), (14, 2, 0.46)] +FLOW = [ + (0, 0, 0.6), (1, 2, 0.36), (2, 4, 0.46), (3, 5, 0.4), (4, 4, 0.5), (5, 2, 0.34), (6, 3, 0.44), + (8, 1, 0.56), (9, 3, 0.36), (10, 4, 0.46), (11, 5, 0.4), (12, 3, 0.5), (13, 2, 0.34), (14, 1, 0.42), +] +CODA = [(0, 4, 0.5), (3, 2, 0.42), (6, 1, 0.36), (8, 0, 0.3)] +ARP = {"A": SPARSE, "A1": SPARSE, "B": FALL, "A2": FLOW} + +# 旋律(颤音琴):(从头数第几拍, 音高, 拍数, 力度),只在 A'、B 两段 +MELODY = [ + (32, 81, 3, 0.5), (35, 78, 1, 0.4), (36, 76, 4, 0.45), + (40, 74, 2, 0.45), (42, 78, 2, 0.42), (44, 81, 4, 0.48), + (48, 83, 3, 0.5), (51, 81, 1, 0.4), (52, 78, 4, 0.45), + (56, 76, 3, 0.45), (59, 74, 1, 0.38), (60, 76, 4, 0.42), + (64, 86, 2, 0.48), (66, 83, 2, 0.42), (68, 81, 4, 0.45), + (72, 85, 2, 0.46), (74, 81, 2, 0.4), (76, 78, 4, 0.44), + (80, 83, 2, 0.46), (82, 79, 2, 0.4), (84, 78, 4, 0.44), + (88, 76, 3, 0.42), (91, 78, 1, 0.36), (92, 76, 4, 0.4), +] + +TABLE = 4096 +GRID = np.arange(TABLE + 1) + + +def hz(midi: float) -> float: + return 440.0 * 2 ** ((midi - 69) / 12) + + +def at(beat: float) -> int: + return round(beat * BEAT * SR) + + +def add(bus: np.ndarray, start: int, sig: np.ndarray, pan: float = 0.0): + """单声道信号按声像(-1 左 ~ 1 右,等功率)叠加到立体声总线;超出结尾的部分绕回开头""" + gains = (np.cos((pan + 1) * np.pi / 4), np.sin((pan + 1) * np.pi / 4)) + pos, i = start % N, 0 + while i < len(sig): + take = min(len(sig) - i, N - pos) + for ch in (0, 1): + bus[ch, pos : pos + take] += sig[i : i + take] * gains[ch] + i += take + pos = 0 + + +def spectral(x: np.ndarray, gain) -> np.ndarray: + """频域滤波(整段循环信号,零相位)""" + f = np.fft.rfftfreq(x.shape[-1], 1 / SR) + return np.fft.irfft(np.fft.rfft(x) * gain(f), x.shape[-1]) + + +def lowpass(x: np.ndarray, fc: float, order: int = 2) -> np.ndarray: + return spectral(x, lambda f: 1 / np.sqrt(1 + (f / fc) ** (2 * order))) + + +# ───────── 乐器 ───────── +def pad_tables(f: float, cutoffs: tuple = (600.0, 2400.0)): + """铺底音色:带低通的锯齿波谐波(暗 / 亮两张波表,按亮度混合),谐波相位随机,听起来不刺""" + x = np.arange(TABLE) / TABLE + phases = np.random.default_rng(round(f * 1000)).uniform(0, 2 * np.pi, 64) + tables = [] + for fc in cutoffs: + tab = np.zeros(TABLE) + for k in range(1, 64): + w = (1 / k) / np.sqrt(1 + (k * f / fc) ** 4) + if k * f > 9000 or w < 1e-4: + break + tab += w * np.sin(2 * np.pi * k * x + phases[k]) + tables.append(np.append(tab, tab[0])) + return tables + + +def render_pad(bus: np.ndarray, start: int, f: float, hold: float, tau: float, bright: float, attack: float = 1.6, cutoffs: tuple = (600.0, 2400.0)): + """三个声部(-7 / 0 / +7 音分)左中右铺开,缓起(attack 秒),结束后按 tau 慢慢消失""" + n = int((hold + 5 * tau) * SR) + t = np.arange(n) / SR + rise = np.clip(t / attack, 0, 1) + env = (0.5 - 0.5 * np.cos(np.pi * rise)) * np.where(t < hold, 1.0, np.exp(-(t - hold) / tau)) + # 亮度缓慢起伏,周期是循环长度的 1/5(首尾一致) + abs_t = start / SR + t + mix = np.clip(bright + 0.15 * np.sin(2 * np.pi * abs_t / (LOOP_S / 5)), 0, 1) + dark, light = pad_tables(f, cutoffs) + for cents, pan, gain in ((-7, -0.6, 0.8), (0, 0.0, 1.0), (7, 0.6, 0.8)): + fv = f * 2 ** (cents / 1200) + ph = ((fv * t + rng.uniform()) % 1.0) * TABLE + d = np.interp(ph, GRID, dark) + b = np.interp(ph, GRID, light) + add(bus, start, (d + (b - d) * mix) * env * gain, pan) + + +def rhodes(f: float, vel: float, hold: float) -> np.ndarray: + """电钢琴(FM):1:1 调制出温暖的音体,14 倍频的短促调制做出“叮”的击弦声;两个载波差 2.6 音分,带一点合唱感""" + n = int((hold + 2.5) * SR) + t = np.arange(n) / SR + tau = float(np.clip(2.6 * (220 / f) ** 0.5, 0.7, 2.6)) + env = (1 - np.exp(-t / 0.003)) * np.exp(-t / tau) * np.where(t < hold, 1.0, np.exp(-(t - hold) / 0.3)) + index = vel * (1.9 * np.exp(-t / 0.15) + 0.6 * np.exp(-t / 1.4)) + w = 2 * np.pi * f * t + w2 = w * 1.0015 + body = 0.62 * np.sin(w + index * np.sin(w)) + 0.38 * np.sin(w2 + 0.8 * index * np.sin(w2)) + tine = 0.22 * np.exp(-t / 0.05) * np.sin(w + 1.2 * np.exp(-t / 0.04) * np.sin(14 * w)) + return (body + tine) * env * vel + + +def vibes(f: float, vel: float, hold: float) -> np.ndarray: + """颤音琴:基音长衰减 + 4 倍泛音短衰减,带轻微的马达颤音""" + n = int((hold + 3.0) * SR) + t = np.arange(n) / SR + w = 2 * np.pi * f * t + tone = np.exp(-t / 3.0) * np.sin(w) + 0.17 * np.exp(-t / 0.5) * np.sin(4 * w) + trem = 1 - 0.14 * (0.5 - 0.5 * np.cos(2 * np.pi * 3.2 * t)) + env = (1 - np.exp(-t / 0.004)) * np.where(t < hold, 1.0, np.exp(-(t - hold) / 0.5)) + return tone * trem * env * vel + + +def bass(f: float, vel: float, hold: float) -> np.ndarray: + """柔和的低音:正弦加少量 2~4 次谐波(手机外放也能隐约听到)""" + n = int((hold + 1.2) * SR) + t = np.arange(n) / SR + rise = np.clip(t / 0.05, 0, 1) + env = (0.5 - 0.5 * np.cos(np.pi * rise)) * (0.7 + 0.3 * np.exp(-t / 0.6)) * np.where(t < hold, 1.0, np.exp(-(t - hold) / 0.25)) + w = 2 * np.pi * f * t + return (np.sin(w) + 0.3 * np.sin(2 * w) + 0.12 * np.sin(3 * w) + 0.05 * np.sin(4 * w)) * env * vel + + +# ───────── 编曲 ───────── +def humanize() -> int: + return int(np.clip(rng.normal(0, 0.006), -0.015, 0.015) * SR) + + +def arrange(): + pad, halo, keys, vib, low = (np.zeros((2, N)) for _ in range(5)) + last_slot = len(PROGRESSION) - 1 + for i, name in enumerate(PROGRESSION): + voicing, root, arp = CHORDS[name] + section = SECTIONS[i] + beat0 = i * 8 + last = i == last_slot + # 铺底:最后一个和弦只撑一小节,然后慢慢淡出,把循环点留在最轻的地方 + hold, tau = (4 if last else 8) * BEAT, 1.8 if last else 1.3 + for m in voicing: + render_pad(pad, at(beat0), hz(m), hold, tau, BRIGHTNESS[section]) + if section != "A": + # 高八度的光晕:更慢地浮起来,给中高频一点空气感 + render_pad(halo, at(beat0), hz(m + 12), hold, tau, 0.6, attack=2.5, cutoffs=(1200.0, 4000.0)) + add(low, at(beat0), bass(hz(root), 0.8, 4 * BEAT - 0.12)) + if not last: + add(low, at(beat0 + 4), bass(hz(root), 0.62, 4 * BEAT - 0.12)) + for slot, k, vel in CODA if last else ARP[section]: + m = arp[k] + note = rhodes(hz(m), vel + rng.uniform(-0.03, 0.03), 2.5 * BEAT) + add(keys, at(beat0 + slot / 2) + humanize(), note, float(np.clip((m - 72) / 24, -0.35, 0.35))) + for beat, m, beats_, vel in MELODY: + add(vib, at(beat) + humanize(), vibes(hz(m), vel, beats_ * BEAT - 0.05), 0.08) + return pad, halo, keys, vib, low + + +# ───────── 空间效果 ───────── +def reverb_ir(seconds: float = 4.5) -> np.ndarray: + """合成厅堂混响:去相关的左右声道噪声,按频段衰减(低频长、高频短),20ms 预延迟""" + n = int(seconds * SR) + t = np.arange(n) / SR + f = np.fft.rfftfreq(n, 1 / SR) + bands = [(0, 250, 3.6), (250, 1000, 3.0), (1000, 3500, 2.3), (3500, 8000, 1.5), (8000, SR, 0.8)] + ir = np.zeros((2, n)) + for ch in (0, 1): + spec = np.fft.rfft(rng.standard_normal(n)) + for lo, hi, rt60 in bands: + band = np.fft.irfft(np.where((f >= lo) & (f < hi), spec, 0), n) + ir[ch] += band * 10 ** (-3 * t / rt60) + ir *= 1 - np.exp(-t / 0.03) + ir = np.concatenate([np.zeros((2, int(0.02 * SR))), ir], axis=1) + return ir / np.sqrt((ir**2).sum(axis=1, keepdims=True)) + + +def circular_reverb(send: np.ndarray, ir: np.ndarray) -> np.ndarray: + """循环卷积:结尾的混响尾巴绕回开头,和下一遍循环无缝衔接""" + mid_l = 0.7 * send[0] + 0.3 * send[1] + mid_r = 0.3 * send[0] + 0.7 * send[1] + return np.stack([np.fft.irfft(np.fft.rfft(x) * np.fft.rfft(h, N), N) for x, h in ((mid_l, ir[0]), (mid_r, ir[1]))]) + + +def pingpong(send: np.ndarray, beats: float = 0.75, feedback: float = 0.32, taps: int = 6) -> np.ndarray: + """附点八分的左右乒乓延迟,回声逐次变暗(循环移位,同样首尾相接)""" + d = at(beats) + mono = lowpass(send.mean(axis=0), 2800) + out = np.zeros((2, N)) + for k in range(1, taps + 1): + out[k % 2] += np.roll(mono, k * d) * feedback**k + mono = lowpass(mono, 4000, 1) + return out + + +def rms(x: np.ndarray) -> float: + return float(np.sqrt(np.mean(x**2))) + + +def db(v: float) -> float: + return 20 * np.log10(max(v, 1e-12)) + + +def level(x: np.ndarray, target_db: float) -> np.ndarray: + return x * (10 ** (target_db / 20) / rms(x)) + + +def mixdown() -> np.ndarray: + pad, halo, keys, vib, low = arrange() + pad = level(pad, -21) + halo = level(halo, -31) + keys = level(keys, -21) + vib = level(vib, -23) + low = level(low, -28) + dry = pad + halo + keys + vib + low + wet = circular_reverb(0.5 * pad + 0.9 * halo + 0.6 * keys + 0.7 * vib + 0.08 * low, reverb_ir()) + echo = pingpong(keys + 0.6 * vib) + mix = dry + level(wet, db(rms(dry)) - 4.5) + level(echo, db(rms(keys)) - 11) + # 母带:切掉 30Hz 以下的闷响 + mix = spectral(mix, lambda f: 1 / np.sqrt(1 + (30 / np.maximum(f, 1)) ** 4)) + mix = level(mix, TARGET_RMS_DB) + peak = np.abs(mix).max() + if peak > PEAK_LIMIT: + # 极少数峰值软限幅(tanh 在小信号区近似线性) + mix = np.tanh(mix / PEAK_LIMIT) * PEAK_LIMIT + fade = int(EDGE_FADE_S * SR) + ramp = 0.5 - 0.5 * np.cos(np.pi * np.arange(fade) / fade) + mix[:, :fade] *= ramp + mix[:, -fade:] *= ramp[::-1] + return mix + + +# ───────── 输出与检查 ───────── +def write_wav(path: Path, mix: np.ndarray): + path.parent.mkdir(parents=True, exist_ok=True) + dither = (rng.uniform(-0.5, 0.5, mix.shape) + rng.uniform(-0.5, 0.5, mix.shape)) / 32767 + pcm = np.clip(np.round((mix + dither) * 32767), -32768, 32767).astype(".png → assets/sheets/<动作>.webp(无损,约为 PNG 的一半大小) + +用法:python3 coach_adopt.py <动作> [动作 ...] +""" +import sys +from pathlib import Path + +from PIL import Image + +ROOT = Path(__file__).resolve().parents[1] + +if __name__ == "__main__": + for move in sys.argv[1:]: + src = ROOT / "work" / "sheets" / f"{move}.png" + im = Image.open(src) + if im.mode != "RGBA" or im.getchannel("A").getextrema()[0] > 0: + raise SystemExit(f"{src.name} 不是透明背景,不能使用") + dst = ROOT / "assets" / "sheets" / f"{move}.webp" + im.save(dst, lossless=True, quality=100, method=6) + print(f"已采用 {src.relative_to(ROOT)} → {dst.relative_to(ROOT)}({im.width}×{im.height})") diff --git a/training-videos/scripts/coach_export.py b/training-videos/scripts/coach_export.py new file mode 100644 index 000000000..09466fa5c --- /dev/null +++ b/training-videos/scripts/coach_export.py @@ -0,0 +1,193 @@ +""" +教练帧导出:对齐后的主帧 → 各端使用的尺寸 + 帧清单 + +用法:python3 coach_export.py (读取 coach_frames.py 的产物 work/frames/) + python3 coach_export.py --from-hd (没有 work/ 时,用仓库里的 1000px 高清帧重新导出小程序帧) +导出: + TUICallKit-Vue3/training/static/coach/*.png 600px 调色板 PNG(轻微锐化),内置进小程序分包(离线兜底) + training-videos/out/coach/*.webp 高清帧:1000px 画布,每个动作裁出人物所在区域,上传到 COS + training-videos/public/coach/hd/*.webp 1000px 整张画布,Remotion 示范视频使用 + TUICallKit-Vue3/training/pose/coachSequences.ts 帧清单、每帧的动作幅度、高清帧裁切框与版本号(自动生成,勿手改) + +手势帧、错误示范帧(STATIC_FRAMES)不随动作帧重新生成,直接用高清帧目录里已有的版本; +错误示范帧只有示范视频用,不打进小程序包(分包有 2MB 上限)。 +""" +import hashlib +import json +import re +import subprocess +import sys +from pathlib import Path + +from PIL import Image, ImageFilter + +ROOT = Path(__file__).resolve().parents[1] +WORK = ROOT / "work" / "frames" +MINI_DIR = ROOT.parent / "TUICallKit-Vue3" / "training" / "static" / "coach" +HD_DIR = ROOT / "public" / "coach" / "hd" +TS_OUT = ROOT.parent / "TUICallKit-Vue3" / "training" / "pose" / "coachSequences.ts" +CDN_DIR = ROOT / "out" / "coach" +MINI_SIZE = 600 +CDN_MARGIN = 12 +# 每个动作的帧名前缀:同一个动作的所有帧共用一个裁切框,帧与帧之间位置不变 +MOVE_PREFIX = {"squat": ("sq_",), "raise": ("ra_",), "jack": ("jl_", "jr_"), "march": ("ml_", "mr_")} + +STATIC_FRAMES = ["idle", "wave", "cheer", "thumbs", "wrong_squat", "wrong_march", "wrong_jack", "wrong_raise"] +MINI_STATIC = ["idle", "wave", "cheer", "thumbs"] +SEQUENCE_ORDER = ["squat", "raise", "jack_l", "jack_r", "march_l", "march_r"] + + +def resize_rgba(im: Image.Image, size: int) -> Image.Image: + """在预乘 Alpha 空间缩放:直接缩放 RGBA 会把透明像素的黑色混进边缘,形成黑边""" + return im.convert("RGBa").resize((size, size), Image.Resampling.LANCZOS).convert("RGBA") + + +def sharpen(im: Image.Image) -> Image.Image: + """手机上帧会被放大 2~3 倍显示:导出时轻微锐化,放大后不发虚(在预乘空间做,边缘不起白边)""" + r, g, b, a = im.convert("RGBa").split() + rgb = Image.merge("RGB", (r, g, b)).filter(ImageFilter.UnsharpMask(radius=1.2, percent=45, threshold=2)) + return Image.merge("RGBa", (*rgb.split(), a)).convert("RGBA") + + +def write_mini(im: Image.Image, name: str): + small = sharpen(resize_rgba(im, MINI_SIZE)) + small.quantize(colors=256, method=Image.Quantize.FASTOCTREE, dither=Image.Dither.FLOYDSTEINBERG).save( + MINI_DIR / f"{name}.png", optimize=True + ) + + +def write_hd(im: Image.Image, name: str): + tmp = HD_DIR / f"{name}.tmp.png" + im.save(tmp) + subprocess.run(["cwebp", "-quiet", "-q", "90", "-alpha_q", "95", "-m", "6", str(tmp), "-o", str(HD_DIR / f"{name}.webp")], check=True) + tmp.unlink() + + +def union_crop(images: list) -> tuple: + """同一个动作所有帧的人物区域并集,外扩一点边距""" + box = None + for im in images: + b = im.getchannel("A").point(lambda v: 255 if v > 8 else 0).getbbox() + box = b if box is None else (min(box[0], b[0]), min(box[1], b[1]), max(box[2], b[2]), max(box[3], b[3])) + w, h = images[0].size + return max(0, box[0] - CDN_MARGIN), max(0, box[1] - CDN_MARGIN), min(w, box[2] + CDN_MARGIN), min(h, box[3] + CDN_MARGIN) + + +def write_cdn(motion: list) -> dict: + """高清帧:只裁出人物区域(整张 1000px 画布解码后 4MB/帧,一个动作几十帧手机吃不消),WebP""" + CDN_DIR.mkdir(parents=True, exist_ok=True) + for f in CDN_DIR.glob("*.webp"): + f.unlink() + crops = {} + for move, prefixes in MOVE_PREFIX.items(): + names = [n for n in motion if n.startswith(prefixes)] + images = {n: Image.open(WORK / "master" / f"{n}.png").convert("RGBA") for n in names} + box = union_crop(list(images.values())) + size = next(iter(images.values())).size + crops[move] = [round(box[0] / size[0], 4), round(box[1] / size[1], 4), round(box[2] / size[0], 4), round(box[3] / size[1], 4)] + for n, im in images.items(): + tmp = CDN_DIR / f"{n}.tmp.png" + im.crop(box).save(tmp) + subprocess.run( + ["cwebp", "-quiet", "-q", "88", "-alpha_q", "92", "-m", "6", "-sharp_yuv", str(tmp), "-o", str(CDN_DIR / f"{n}.webp")], + check=True, + ) + tmp.unlink() + digest = hashlib.sha1() + for f in sorted(CDN_DIR.glob("*.webp")): + digest.update(f.read_bytes()) + return {"version": digest.hexdigest()[:10], "crops": crops} + + +def write_manifest(sequences: dict, hd: dict): + lines = [ + "/**", + " * 教练动作帧清单(由 training-videos/scripts/coach_export.py 自动生成,勿手改)", + " *", + " * 每个序列按动作幅度 0→1 排列,全部是 AI 原图帧(内置在 training/static/coach),", + " * 同一个动作出自同一张设定稿;右侧动作由左侧镜像换边生成。amounts 是每帧实测的动作幅度。", + " */", + "export type CoachSequence = {", + " /** 帧名,按动作幅度 0→1 排列 */", + " frames: string[]", + " /** 每帧的动作幅度(0 = 起始姿势,1 = 动作到位),与 frames 一一对应、严格递增 */", + " amounts: number[]", + "}", + "", + "export const COACH_SEQUENCES: Record = {", + ] + for name in SEQUENCE_ORDER: + seq = sequences[name] + lines.append(f" {name}: {{") + lines.append(f" frames: [{', '.join(repr(f) for f in seq['frames'])}],") + lines.append(f" amounts: [{', '.join(format(a, 'g') for a in seq['amounts'])}],") + lines.append(" },") + lines.append("}") + lines += [ + "", + "/**", + " * 高清帧(training-videos/out/coach 上传到 COS):每个动作裁出人物所在区域,", + " * crops 是裁切框在整张画布上的比例 [左, 上, 右, 下];version 随内容变化,用来刷新 CDN 缓存。", + " */", + "export const COACH_HD: { version: string; crops: Record } = {", + f" version: '{hd['version']}',", + " crops: {", + ] + for move, c in hd["crops"].items(): + lines.append(f" {move}: [{', '.join(format(v, 'g') for v in c)}],") + lines += [" },", "}"] + TS_OUT.write_text("\n".join(lines) + "\n") + + +def check(sequences: dict): + for name in SEQUENCE_ORDER: + seq = sequences.get(name) + if not seq: + raise SystemExit(f"缺少序列 {name},先运行 coach_frames.py") + a = seq["amounts"] + if len(a) != len(seq["frames"]) or a[0] != 0 or a[-1] != 1 or any(y <= x for x, y in zip(a, a[1:])): + raise SystemExit(f"序列 {name} 的幅度必须从 0 严格递增到 1:{a}") + + +def export_all(): + sequences = json.loads((WORK / "sequences.json").read_text()) + check(sequences) + motion = sorted({f for s in sequences.values() for f in s["frames"]}) + missing_static = [n for n in STATIC_FRAMES if not (HD_DIR / f"{n}.webp").exists()] + if missing_static: + raise SystemExit(f"高清帧目录缺少手势 / 错误示范帧:{', '.join(missing_static)}") + MINI_DIR.mkdir(parents=True, exist_ok=True) + HD_DIR.mkdir(parents=True, exist_ok=True) + # 清掉旧版本留下、已不再使用的帧 + for d, ext, keep in ((MINI_DIR, "png", set(motion) | set(MINI_STATIC)), (HD_DIR, "webp", set(motion) | set(STATIC_FRAMES))): + for f in d.glob(f"*.{ext}"): + if f.stem not in keep: + f.unlink() + for name in motion: + im = Image.open(WORK / "master" / f"{name}.png").convert("RGBA") + write_mini(im, name) + write_hd(im, name) + for name in MINI_STATIC: + write_mini(Image.open(HD_DIR / f"{name}.webp").convert("RGBA"), name) + hd = write_cdn(motion) + write_manifest(sequences, hd) + size = sum(f.stat().st_size for f in MINI_DIR.glob("*.png")) + cdn = sum(f.stat().st_size for f in CDN_DIR.glob("*.webp")) + print(f"已导出 {len(motion)} 个动作帧 + {len(MINI_STATIC)} 个手势帧,小程序帧共 {size / 1024:.0f}KB") + print(f"高清帧 {len(motion)} 张共 {cdn / 1024:.0f}KB → {CDN_DIR.relative_to(ROOT)}(上传到 COS),版本 {hd['version']};已更新 coachSequences.ts") + + +def export_from_hd(): + MINI_DIR.mkdir(parents=True, exist_ok=True) + names = sorted(f.stem for f in HD_DIR.glob("*.webp") if not f.stem.startswith("wrong_")) + listed = set(re.findall(r"'([a-z_0-9]+)'", TS_OUT.read_text())) | set(MINI_STATIC) + missing = sorted(listed - set(names)) + if missing: + raise SystemExit(f"高清帧目录缺少:{', '.join(missing)}") + for name in names: + write_mini(Image.open(HD_DIR / f"{name}.webp").convert("RGBA"), name) + print(f"已从高清帧重新导出 {len(names)} 个小程序帧") + + +if __name__ == "__main__": + export_from_hd() if "--from-hd" in sys.argv else export_all() diff --git a/training-videos/scripts/coach_facelock.py b/training-videos/scripts/coach_facelock.py new file mode 100644 index 000000000..735f20e9e --- /dev/null +++ b/training-videos/scripts/coach_facelock.py @@ -0,0 +1,65 @@ +""" +锁脸:同一个动作的所有帧都用第一帧(站姿)的脸 + +每一帧是 AI 单独画的,五官、发际线每帧都有细微差别,按十几帧每秒播放时脸会“一闪一闪”。 +这里按模板匹配找到每帧的脸,把整帧水平平移让头对齐第一帧(头不再左右晃), +再把第一帧的脸(额头刘海到下巴)用羽化椭圆贴上去。头顶轮廓、马尾不动,避免盖住举过头的手臂。 +""" +import cv2 +import numpy as np + +BG = np.array([244, 248, 246], np.float32) + + +def _on_bg(rgba: np.ndarray) -> np.ndarray: + f = rgba.astype(np.float32) + return f[..., :3] * (f[..., 3:4] / 255) + BG * (1 - f[..., 3:4] / 255) + + +def _face(kp: dict): + """脸中心(两眼中点与鼻子之间)和眼距""" + ex = (kp["leye"][0] + kp["reye"][0]) / 2 + ey = (kp["leye"][1] + kp["reye"][1]) / 2 + return ex, ey + 0.6 * (kp["nose"][1] - ey), abs(kp["leye"][0] - kp["reye"][0]) + + +def _shift_x(rgba: np.ndarray, dx: int) -> np.ndarray: + out = np.zeros_like(rgba) + if dx > 0: + out[:, dx:] = rgba[:, :-dx] + elif dx < 0: + out[:, :dx] = rgba[:, -dx:] + else: + out[:] = rgba + return out + + +def lock_faces(frames: list, kps: list): + """frames:对齐后的主帧(RGBA);kps:对应关键点(画布坐标)。返回处理后的帧与平移后的关键点""" + ref = frames[0] + cx, cy, d = _face(kps[0]) + h = int(round(2.2 * d)) # 模板半边长:盖住整个脸和刘海 + x0, y0 = int(round(cx)) - h, int(round(cy)) - h + tpl = _on_bg(ref)[y0 : y0 + 2 * h, x0 : x0 + 2 * h] + src = ref[y0 : y0 + 2 * h, x0 : x0 + 2 * h].astype(np.float32) + # 羽化椭圆:上到刘海发际线、下到下巴,左右到脸颊 + yy, xx = np.mgrid[0 : 2 * h, 0 : 2 * h].astype(np.float32) + ex, ey = cx - x0, cy - y0 - 0.3 * d + ell = (((xx - ex) / (1.35 * d)) ** 2 + ((yy - ey) / (1.75 * d)) ** 2 <= 1).astype(np.float32) + mask = cv2.GaussianBlur(ell, (0, 0), 0.2 * d)[..., None] + + out_frames, out_kps = [], [] + for f, kp in zip(frames, kps): + fx, fy, _ = _face(kp) + pad = 40 + sx0, sy0 = int(round(fx)) - h - pad, int(round(fy)) - h - pad + win = _on_bg(f)[sy0 : sy0 + 2 * (h + pad), sx0 : sx0 + 2 * (h + pad)] + _, _, _, loc = cv2.minMaxLoc(-cv2.matchTemplate(win, tpl, cv2.TM_SQDIFF)) + tx, ty = sx0 + loc[0], sy0 + loc[1] + dx = x0 - tx + g = _shift_x(f, dx).astype(np.float32) + region = g[ty : ty + 2 * h, x0 : x0 + 2 * h] + region[:] = src * mask + region * (1 - mask) + out_frames.append(np.clip(g, 0, 255).round().astype(np.uint8)) + out_kps.append({k: (x + dx, y, s) for k, (x, y, s) in kp.items()}) + return out_frames, out_kps diff --git a/training-videos/scripts/coach_frames.py b/training-videos/scripts/coach_frames.py new file mode 100644 index 000000000..1df6de1d8 --- /dev/null +++ b/training-videos/scripts/coach_frames.py @@ -0,0 +1,328 @@ +""" +教练动作帧流水线:连续动作设定稿 → 对齐的高清主帧 + 帧清单 + +输入:assets/sheets/<动作>.webp —— Codex 生成的透明背景设定稿,一张图里按阅读顺序画一个动作的全部帧 + (同一次渲染,前后帧的脸和衣服完全一致,播放时不会“闪”) +处理: + 1. 连通域切出每个人物,丢弃远处的彩色噪点 + 2. Real-ESRGAN 4 倍超分后再缩到主帧尺寸(设定稿里人物只有 300~450px 高) + 3. Keypoint R-CNN 测关键点:肩部中点对齐画布中线,脚底对齐地平线 + 4. 按实测动作幅度挑帧:去掉倒退、重复的帧,幅度归一化到 0~1(播放时按幅度查帧) + 5. 锁脸:所有帧都用第一帧的脸,并按头部对齐,播放时脸不会“一闪一闪”、头不会左右晃 + 6. 插帧:相邻两帧之间用 RIFE 补中间帧(有残影的跳过),再按幅度均匀挑出目标帧数(包体有上限) + 7. 左右交替的动作只画左侧,右侧由镜像换边生成(头部保留原图方向,马尾不会左右跳) +输出:work/frames/master/*.png(1000px 主帧)、work/frames/sequences.json、work/preview/contact_*.jpg + +用法:python3 coach_frames.py [动作 ...] (默认处理全部动作) +""" +import json +import sys +from pathlib import Path + +import cv2 +import numpy as np +from PIL import Image + +from coach_facelock import lock_faces +from coach_interp import densify, thin +from coach_mirror import mirror_frame +from coach_vision import arm_angle, keypoints, shoulder_center, upscale4 + +ROOT = Path(__file__).resolve().parents[1] +SHEETS = ROOT / "assets" / "sheets" +WORK = ROOT / "work" +MASTER = WORK / "frames" / "master" +PREVIEW = WORK / "preview" + +CANVAS = 1000 # 主帧画布边长 +STAND_H = 720 # 标准站姿身高 +GROUND_Y = 950 # 地平线 +ALPHA_ON = 40 +MIN_GAP = 0.015 # 幅度差小于该值的帧视为重复 + +# 每个动作:设定稿版式(行、列)、取第几个人物(阅读顺序)、幅度指标、帧名前缀、比例基准、镜像生成的另一侧 +# scale = "first":整张设定稿按第一个人物(站姿)统一缩放(下蹲、开合跳的身高本来就会变) +# scale = "each":每个人物各自缩放到标准身高(全程站直的动作,顺带消除模型画的大小误差) +# densify:幅度差大于该值的相邻两帧之间插一帧;frames:最终按幅度均匀挑出的帧数 +# 帧数按“动作过程约每秒 20 帧以上”定:深蹲、侧平举动作慢(约 1.1 秒)要多;开合跳、高抬腿快(约 0.4 秒)少一些 +MOVES = { + "squat": dict(grid=(2, 9), take=range(18), metric="hip_drop", prefix="sq", seq="squat", scale="first", densify=0.03, frames=24), + "raise": dict(grid=(3, 6), take=range(18), metric="arms", prefix="ra", seq="raise", scale="each", densify=0.03, frames=24), + "jack": dict(grid=(3, 6), take=range(18), metric="arms", prefix="jl", seq="jack_l", scale="first", frames=10, mirror=("jack_r", "jr")), + "march": dict(grid=(2, 9), take=range(9), metric="knee_l", prefix="ml", seq="march_l", scale="each", densify=0.03, frames=13, mirror=("march_r", "mr")), +} + + +def bbox(mask: np.ndarray): + ys, xs = np.where(mask) + return xs.min(), ys.min(), xs.max() + 1, ys.max() + 1 + + +def _centers_inside(lo, hi, n, size): + return [(k + 0.5) * size / n for k in range(n) if lo < (k + 0.5) * size / n < hi] + + +def _cut_positions(profile, centers): + """相邻两个格子中心之间,像素最少的位置""" + cuts = [] + for a, b in zip(centers, centers[1:]): + lo, hi = int(a + (b - a) * 0.25), int(b - (b - a) * 0.25) + cuts.append(lo + int(np.argmin(profile[lo:hi]))) + return cuts + + +def row_grounds(stats, mains, rows: int, height: int): + """每一行的地平线 = 该行没有粘连的人物脚底的中位数""" + row_h = height / rows + bottoms = {} + for i in mains: + y0, y1 = stats[i, 1], stats[i, 1] + stats[i, 3] + if y1 - y0 < row_h * 1.2: + bottoms.setdefault(int((y0 + y1) / 2 // row_h), []).append(y1) + return {r: int(np.median(b)) for r, b in bottoms.items()} + + +def _split_rows(mask: np.ndarray, rows: int, grounds: dict): + """上下粘连(举过头顶的手碰到上一行的脚):沿上一行的地平线切开,鞋底以下一定属于下一行的人物""" + _, y0, _, y1 = bbox(mask) + height = mask.shape[0] + centers = _centers_inside(y0, y1, rows, height) + if len(centers) <= 1: + return [mask] + first = int(centers[0] // (height / rows)) + fallback = _cut_positions(mask.sum(1), centers) + cuts = [grounds.get(first + k, fallback[k]) for k in range(len(centers) - 1)] + parts = [] + for a, b in zip([y0] + cuts, cuts + [y1]): + part = np.zeros_like(mask) + part[a:b] = mask[a:b] + if part.any(): + parts.append(part) + return parts + + +def _split_cols(mask: np.ndarray, cols: int): + """ + 左右粘连(平举的指尖碰到隔壁):腐蚀断开细小的接触点得到各人物主体,每个像素归给最近的主体; + 接触面太大腐蚀不开时,按格子中心之间像素最少的位置直线切开 + """ + x0, _, x1, _ = bbox(mask) + centers = _centers_inside(x0, x1, cols, mask.shape[1]) + expected = len(centers) + if expected <= 1: + return [mask] + m8 = mask.astype(np.uint8) + for k in (3, 5, 7, 9, 11): + n, lab, stats, _ = cv2.connectedComponentsWithStats(cv2.erode(m8, np.ones((k, k), np.uint8)), connectivity=8) + if n <= 1: + break + areas = stats[1:, cv2.CC_STAT_AREA] + big = [i + 1 for i in np.argsort(-areas)[:expected] if areas[i] > areas.max() * 0.2] + if len(big) == expected: + dist = np.stack([cv2.distanceTransform((lab != i).astype(np.uint8), cv2.DIST_L2, 3) for i in big]) + nearest = np.argmin(dist, axis=0) + return [mask & (nearest == j) for j in range(expected)] + cuts = _cut_positions(mask.sum(0), centers) + parts = [] + for a, b in zip([x0] + cuts, cuts + [x1]): + part = np.zeros_like(mask) + part[:, a:b] = mask[:, a:b] + parts.append(part) + return parts + + +def _reassign(parts): + """切开后,脱离主体的碎块(被切断的指尖)归还给与它接触最多的那个人物""" + mains, frags = [], [] + for p in parts: + n, lab, stats, _ = cv2.connectedComponentsWithStats(p.astype(np.uint8), connectivity=8) + k = 1 + int(np.argmax(stats[1:, cv2.CC_STAT_AREA])) + mains.append(lab == k) + frags += [lab == j for j in range(1, n) if j != k] + grown = [cv2.dilate(m.astype(np.uint8), np.ones((5, 5), np.uint8)) > 0 for m in mains] + for f in frags: + contact = [int((g & f).sum()) for g in grown] + if max(contact) > 0: + best = int(np.argmax(contact)) + mains[best] = mains[best] | f + return mains + + +def find_figures(rgba: np.ndarray, grid): + """连通域切出每个人物(按阅读顺序);紧挨人物的碎块(发梢、指尖)并入,远处的噪点丢弃""" + on = (rgba[..., 3] > ALPHA_ON).astype(np.uint8) + n, lab, stats, _ = cv2.connectedComponentsWithStats(on, connectivity=8) + comps = sorted(range(1, n), key=lambda i: -stats[i, cv2.CC_STAT_AREA]) + big = stats[comps[0], cv2.CC_STAT_AREA] + mains = [i for i in comps if stats[i, cv2.CC_STAT_AREA] >= big * 0.18] + rows_n, cols_n = grid + grounds = row_grounds(stats, mains, rows_n, rgba.shape[0]) + figs = [] + for i in mains: + parts = [p for part in _split_rows(lab == i, rows_n, grounds) for p in _split_cols(part, cols_n)] + figs += _reassign(parts) if len(parts) > 1 else parts + union = np.logical_or.reduce(figs) + box = [bbox(m) for m in figs] + cy = [(b[1] + b[3]) / 2 for b in box] + rows = [] + for i in sorted(range(len(figs)), key=lambda i: cy[i]): + if rows and cy[i] - np.mean([cy[j] for j in rows[-1]]) < (box[rows[-1][0]][3] - box[rows[-1][0]][1]) / 2: + rows[-1].append(i) + else: + rows.append([i]) + ordered = [i for row in rows for i in sorted(row, key=lambda i: box[i][0])] + dist = [cv2.distanceTransform((~m).astype(np.uint8), cv2.DIST_L2, 3) for m in figs] + extra = [np.zeros_like(m) for m in figs] + for c in comps: + if c in mains or stats[c, cv2.CC_STAT_AREA] < 12: + continue + pts = lab == c + near = min(range(len(figs)), key=lambda k: dist[k][pts].min()) + if dist[near][pts].min() <= 6: + extra[near] |= pts + result = [] + for i in ordered: + mask = cv2.dilate((figs[i] | extra[i]).astype(np.uint8), np.ones((5, 5), np.uint8)) > 0 + mask &= ~(union & ~figs[i]) # 外扩时不要把相邻人物的像素带进来 + ys, xs = np.where(mask) + x0, y0, x1, y1 = xs.min(), ys.min(), xs.max() + 1, ys.max() + 1 + fig = rgba[y0:y1, x0:x1].copy() + fig[..., 3] = (fig[..., 3] * mask[y0:y1, x0:x1]).astype(np.uint8) + ys, xs = np.where(fig[..., 3] > 8) + result.append(fig[ys.min() : ys.max() + 1, xs.min() : xs.max() + 1]) + return result + + +def resize_rgba(arr: np.ndarray, size) -> np.ndarray: + """在预乘 Alpha 空间缩放:直接缩放 RGBA 会把透明像素的黑色混进边缘""" + im = Image.fromarray(arr, "RGBA").convert("RGBa").resize(size, Image.Resampling.LANCZOS) + return np.array(im.convert("RGBA")) + + +def metric(kind: str, kp: dict) -> float: + if kind == "hip_drop": + return (kp["lhip"][1] + kp["rhip"][1]) / 2 + if kind == "arms": + return (arm_angle(kp, "l") + arm_angle(kp, "r")) / 2 + if kind == "knee_l": + return -kp["lkn"][1] + raise ValueError(kind) + + +def select(values): + """按幅度排序,去掉倒退、重复的帧;第一帧(站姿)= 0,幅度最大的帧 = 1""" + v0, v1 = values[0], max(values) + norm = [(v - v0) / (v1 - v0) for v in values] + peak = max(range(len(values)), key=lambda i: norm[i]) + chosen = [0] + for i in sorted(range(1, len(values)), key=lambda i: (norm[i], i)): + if norm[i] - norm[chosen[-1]] >= MIN_GAP: + chosen.append(i) + if chosen[-1] != peak: + chosen[-1] = peak + return chosen, [round(norm[i], 3) for i in chosen] + + +def place(up: np.ndarray, kp_up: dict, scale: float): + """把 4 倍超分后的人物放到主帧画布:肩部中点对齐中线,脚底对齐地平线;返回画布与画布坐标下的关键点""" + f = scale / 4 + w, h = max(1, round(up.shape[1] * f)), max(1, round(up.shape[0] * f)) + small = resize_rgba(up, (w, h)) + sx, _ = shoulder_center(kp_up) + left = round(CANVAS / 2 - sx * f) + top = GROUND_Y - h + canvas = np.zeros((CANVAS, CANVAS, 4), np.uint8) + dx0, dy0 = max(0, left), max(0, top) + sx0, sy0 = dx0 - left, dy0 - top + cw, ch = min(w - sx0, CANVAS - dx0), min(h - sy0, CANVAS - dy0) + if cw < w or ch < h: + print(f" ! 人物超出画布(left={left}, top={top}, {w}×{h})", file=sys.stderr) + canvas[dy0 : dy0 + ch, dx0 : dx0 + cw] = small[sy0 : sy0 + ch, sx0 : sx0 + cw] + kp = {k: (x * f + left, y * f + top, s) for k, (x, y, s) in kp_up.items()} + return canvas, kp + + +def strip_foreign_skin(canvas: np.ndarray, kp: dict): + """脚踝以下只有鞋子,不会有皮肤:出现的肤色像素是下一行人物举起的指尖(设定稿里碰到了鞋),去掉""" + y = int(max(kp["lan"][1], kp["ran"][1]) + 6) + rgb = canvas[y:, :, :3].astype(np.int32) + skin = (rgb[..., 0] > 150) & (rgb[..., 0] - rgb[..., 2] > 50) & (rgb[..., 0] - rgb[..., 1] > 20) & (canvas[y:, :, 3] > 0) + skin = cv2.dilate(skin.astype(np.uint8), np.ones((5, 5), np.uint8)) > 0 + canvas[y:][skin] = 0 + + +def process(move: str, cfg: dict, sequences: dict): + rgba = np.array(Image.open(SHEETS / f"{move}.webp").convert("RGBA")) + rgba[rgba[..., 3] < 24] = 0 # 透明背景里的低 alpha 彩色雾点 + figures = find_figures(rgba, cfg["grid"]) + take = list(cfg["take"]) + print(f"{move}: 设定稿 {len(figures)} 个人物,取 {len(take)} 个") + ups, kps = [], [] + for i in take: + up = upscale4(figures[i]) + ups.append(up) + kps.append(keypoints(up)) + heights = [figures[i].shape[0] for i in take] + placed = [] + for up, kp_up, h in zip(ups, kps, heights): + scale = STAND_H / (heights[0] if cfg["scale"] == "first" else h) + canvas, kp = place(up, kp_up, scale) + strip_foreign_skin(canvas, kp) + placed.append((canvas, kp)) + values = [metric(cfg["metric"], kp) for _, kp in placed] + chosen, amounts = select(values) + dropped = [take[i] for i in range(len(take)) if i not in chosen] + print(f" 幅度 {[round(v, 1) for v in values]}") + print(f" 保留 {len(chosen)} 帧,去掉第 {dropped} 个人物(倒退或重复)") + for prefix in [cfg["prefix"]] + ([cfg["mirror"][1]] if "mirror" in cfg else []): + for old in MASTER.glob(f"{prefix}_*.png"): + old.unlink() # 上一次生成的帧数可能更多 + frames, frame_kps = lock_faces([placed[i][0] for i in chosen], [placed[i][1] for i in chosen]) + if cfg.get("densify"): + frames, frame_kps, amounts, added = densify(frames, frame_kps, amounts, cfg["densify"]) + print(f" 插帧 {added} 帧") + if cfg.get("frames"): + keep = thin(amounts, cfg["frames"]) + frames, frame_kps, amounts = [frames[i] for i in keep], [frame_kps[i] for i in keep], [amounts[i] for i in keep] + print(f" 最终 {len(frames)} 帧,幅度 {amounts}") + names = [] + for n, frame in enumerate(frames): + name = f"{cfg['prefix']}_{n:02d}" + Image.fromarray(frame, "RGBA").save(MASTER / f"{name}.png") + names.append(name) + sequences[cfg["seq"]] = {"frames": names, "amounts": amounts} + if "mirror" in cfg: + seq, prefix = cfg["mirror"] + mirrored = [names[0]] # 共用起始站姿,换边时不会跳 + for n in range(1, len(frames)): + name = f"{prefix}_{n:02d}" + Image.fromarray(mirror_frame(frames[n], frame_kps[n]), "RGBA").save(MASTER / f"{name}.png") + mirrored.append(name) + sequences[seq] = {"frames": mirrored, "amounts": amounts} + contact([sequences[cfg["seq"]]["frames"]] + ([sequences[cfg["mirror"][0]]["frames"]] if "mirror" in cfg else []), PREVIEW / f"contact_{move}.jpg") + + +def contact(rows, out: Path, cell=160): + cols = max(len(r) for r in rows) + sheet = Image.new("RGB", (cols * cell, len(rows) * cell), (236, 241, 246)) + for r, names in enumerate(rows): + for c, n in enumerate(names): + im = Image.open(MASTER / f"{n}.png").crop((100, 120, 900, 970)).resize((cell * 800 // 850, cell), Image.Resampling.LANCZOS) + sheet.paste(im, (c * cell + (cell - im.width) // 2, r * cell), im) + sheet.save(out, quality=88) + + +def main(): + moves = sys.argv[1:] or list(MOVES) + MASTER.mkdir(parents=True, exist_ok=True) + PREVIEW.mkdir(parents=True, exist_ok=True) + out = WORK / "frames" / "sequences.json" + sequences = json.loads(out.read_text()) if out.exists() else {} + for move in moves: + process(move, MOVES[move], sequences) + out.write_text(json.dumps(sequences, ensure_ascii=False, indent=2)) + print(f"已写入 {out.relative_to(ROOT)}") + + +if __name__ == "__main__": + main() diff --git a/training-videos/scripts/coach_interp.py b/training-videos/scripts/coach_interp.py new file mode 100644 index 000000000..ae06f6b16 --- /dev/null +++ b/training-videos/scripts/coach_interp.py @@ -0,0 +1,103 @@ +""" +插帧与挑帧 + +插帧:在相邻两帧之间用 RIFE(rife-ncnn-vulkan,v4.6 模型)生成中间帧。 + 帧已经很密、相邻两帧动作很小,插出来的中间帧和原帧难以区分; + 但手从下垂变成握拳这类“外形突变”的地方会插出半透明残影——用中间帧里半透明像素的多少判断, + 比两侧原帧多出 10% 以上就不用。 + RIFE 不处理透明通道:颜色贴在浅灰底上插(边缘就算有一点偏差也是浅灰,在亮色背景上看不出), + 透明度单独插一次,再反解出前景色。 +挑帧:按动作幅度均匀地挑出 N 帧(包体有上限,帧数和清晰度要平衡)。 + +RIFE 首次使用时自动下载到 training-videos/work/models(macOS 版)。 +""" +import subprocess +import tempfile +import urllib.request +import zipfile +from pathlib import Path + +import numpy as np +from PIL import Image + +ROOT = Path(__file__).resolve().parents[1] +MODEL_DIR = ROOT / "work" / "models" +RIFE_URL = "https://github.com/nihui/rife-ncnn-vulkan/releases/download/20221029/rife-ncnn-vulkan-20221029-macos.zip" +RIFE_DIR = MODEL_DIR / "rife-ncnn-vulkan-20221029-macos" +MODEL = "rife-v4.6" +BG = np.array([237, 241, 239], np.float32) +GHOST_RATIO = 1.10 + + +def _rife() -> Path: + exe = RIFE_DIR / "rife-ncnn-vulkan" + if not exe.exists(): + MODEL_DIR.mkdir(parents=True, exist_ok=True) + zip_path = MODEL_DIR / "rife.zip" + print("下载 RIFE …") + urllib.request.urlretrieve(RIFE_URL, zip_path) + with zipfile.ZipFile(zip_path) as z: + z.extractall(MODEL_DIR) + exe.chmod(0o755) + return exe + + +def _run(a: Path, b: Path, out: Path): + subprocess.run([str(_rife()), "-0", str(a), "-1", str(b), "-o", str(out), "-m", str(RIFE_DIR / MODEL), "-s", "0.5"], + check=True, capture_output=True) + + +def interpolate(a: np.ndarray, b: np.ndarray) -> np.ndarray: + """两张 RGBA 主帧 → 正中间的一帧""" + with tempfile.TemporaryDirectory() as tmp: + t = Path(tmp) + for name, f in (("a", a), ("b", b)): + x = f.astype(np.float32) + al = x[..., 3:4] / 255 + Image.fromarray((x[..., :3] * al + BG * (1 - al)).round().astype(np.uint8)).save(t / f"{name}_rgb.png") + Image.fromarray(np.dstack([f[..., 3]] * 3)).save(t / f"{name}_a.png") + _run(t / "a_rgb.png", t / "b_rgb.png", t / "mid_rgb.png") + _run(t / "a_a.png", t / "b_a.png", t / "mid_a.png") + rgb = np.array(Image.open(t / "mid_rgb.png").convert("RGB")).astype(np.float32) + al = np.array(Image.open(t / "mid_a.png").convert("L")).astype(np.float32) / 255 + al = np.where(al < 0.04, 0, al) + fg = np.clip((rgb - (1 - al[..., None]) * BG) / np.maximum(al, 1e-3)[..., None], 0, 255) + return np.dstack([fg, al * 255]).round().astype(np.uint8) + + +def _semi(f: np.ndarray) -> int: + al = f[..., 3] / 255.0 + return int(((al > 0.25) & (al < 0.75)).sum()) + + +def densify(frames: list, kps: list, amounts: list, max_gap: float): + """幅度差大于 max_gap 的相邻两帧之间插一帧(有残影的跳过);返回新的帧、关键点、幅度与插入数""" + out_f, out_k, out_a = [frames[0]], [kps[0]], [amounts[0]] + added = 0 + for i in range(len(frames) - 1): + a, b = frames[i], frames[i + 1] + if amounts[i + 1] - amounts[i] > max_gap: + mid = interpolate(a, b) + if _semi(mid) <= GHOST_RATIO * (_semi(a) + _semi(b)) / 2: + out_f.append(mid) + out_k.append({k: tuple((np.array(kps[i][k]) + np.array(kps[i + 1][k])) / 2) for k in kps[i]}) + out_a.append(round((amounts[i] + amounts[i + 1]) / 2, 3)) + added += 1 + out_f.append(b) + out_k.append(kps[i + 1]) + out_a.append(amounts[i + 1]) + return out_f, out_k, out_a, added + + +def thin(amounts: list, n: int) -> list: + """按动作幅度均匀挑 n 帧(保留首尾),返回下标""" + if len(amounts) <= n: + return list(range(len(amounts))) + picked = [0] + for k in range(1, n - 1): + target = k / (n - 1) + # 在剩余可选范围内找幅度最接近目标的帧,保证下标递增、给后面留够位置 + lo, hi = picked[-1] + 1, len(amounts) - (n - 1 - k) + picked.append(min(range(lo, hi), key=lambda i: abs(amounts[i] - target))) + picked.append(len(amounts) - 1) + return picked diff --git a/training-videos/scripts/coach_mirror.py b/training-videos/scripts/coach_mirror.py new file mode 100644 index 000000000..1cfee25ae --- /dev/null +++ b/training-videos/scripts/coach_mirror.py @@ -0,0 +1,78 @@ +""" +镜像换边:左侧动作帧水平翻转得到右侧动作帧,头部(脸 + 马尾)保留原图方向。 + +左右两侧出自同一张设定稿(同一次渲染),换边时脸、衣服都不会“闪”; +只翻转身体,所以马尾始终在同一侧,不会左右跳。 +""" +import cv2 +import numpy as np + +ALPHA_ON = 40 + + +def _premul(rgba): + f = rgba.astype(np.float32) / 255 + f[..., :3] *= f[..., 3:4] + return f + + +def _unpremul(f): + a = np.maximum(f[..., 3:4], 1e-6) + out = f.copy() + out[..., :3] = np.where(f[..., 3:4] > 0, f[..., :3] / a, 0) + return (np.clip(out, 0, 1) * 255).round().astype(np.uint8) + + +def _mirror(arr, xc): + """以 x = xc 为轴水平翻转(整像素平移,不重采样)""" + w = arr.shape[1] + flipped = arr[:, ::-1] + shift = int(round(2 * xc - (w - 1))) + out = np.zeros_like(arr) + if shift >= 0: + out[:, shift:] = flipped[:, : w - shift] + else: + out[:, : w + shift] = flipped[:, -shift:] + return out + + +def _head_mask(rgba, kp, y_cut): + """肩线以上、包含鼻子的连通区域 = 头 + 脖子 + 马尾上段""" + on = rgba[..., 3] > ALPHA_ON + on[int(y_cut) :] = False + _, lab = cv2.connectedComponents(on.astype(np.uint8), connectivity=8) + k = lab[int(round(kp["nose"][1])), int(round(kp["nose"][0]))] + if k == 0: + raise ValueError("鼻子不在人物区域内") + return lab == k + + +def _hair_mask(rgba, head, sh_y): + """与头部相连的深色像素 = 头发 + 整条马尾(马尾会垂到肩线以下)""" + rgb = rgba[..., :3].astype(np.float32) + lum = 0.3 * rgb[..., 0] + 0.59 * rgb[..., 1] + 0.11 * rgb[..., 2] + dark = (lum < 72) & (rgba[..., 3] > ALPHA_ON) + dark[int(sh_y + 60) :] = False + _, lab = cv2.connectedComponents(dark.astype(np.uint8), connectivity=8) + ids = np.unique(lab[head & dark]) + return np.isin(lab, ids[ids > 0]) + + +def mirror_frame(rgba: np.ndarray, kp: dict) -> np.ndarray: + """rgba:对齐后的主帧;kp:该帧关键点(需要 nose / lsh / rsh)""" + xc = (kp["lsh"][0] + kp["rsh"][0]) / 2 + sh_y = min(kp["lsh"][1], kp["rsh"][1]) + y_cut = sh_y - 14 + head = _head_mask(rgba, kp, y_cut) + hair = _hair_mask(rgba, head, sh_y) + # 头部(肩线以上)用原图,靠近肩线处渐变到翻转后的身体 + region = cv2.dilate((head | _mirror(head, xc)).astype(np.uint8), np.ones((9, 9), np.uint8)) > 0 + region[int(y_cut) :] = False + ramp = np.clip((y_cut - np.arange(rgba.shape[0], dtype=np.float32)) / 16, 0, 1)[:, None] + w_head = cv2.GaussianBlur(region.astype(np.float32), (0, 0), 2.5) * ramp + # 原图的马尾保留;翻转过来的马尾(另一侧肩上)连同半透明发梢一起用原图同位置的内容盖掉 + own = cv2.dilate(hair.astype(np.uint8), np.ones((7, 7), np.uint8)) + other = cv2.dilate(_mirror(hair, xc).astype(np.uint8), np.ones((17, 17), np.uint8)) + w_hair = cv2.GaussianBlur(np.maximum(own, other).astype(np.float32), (0, 0), 2) + w = np.maximum(w_head, w_hair)[..., None] + return _unpremul(_premul(_mirror(rgba, xc)) * (1 - w) + _premul(rgba) * w) diff --git a/training-videos/scripts/coach_pose_guide.py b/training-videos/scripts/coach_pose_guide.py new file mode 100644 index 000000000..be886f31c --- /dev/null +++ b/training-videos/scripts/coach_pose_guide.py @@ -0,0 +1,137 @@ +""" +姿势引导图:按均匀的动作幅度,程序化画出每一帧的“人偶”姿势,作为 Codex 生成连续帧的参考。 + +只用文字描述时,模型常把相邻帧画得忽大忽小、左右脚画反;给它一张逐帧的人偶图, +帧间距和左右都由程序决定。被拍者的左侧肢体 = 橙色(在画面右侧),右侧肢体 = 蓝色,躯干/头 = 灰色。 + +用法:python3 coach_pose_guide.py <输出.png> +""" +import math +import sys + +from PIL import Image, ImageDraw + +W, H = 1672, 941 # Codex 生成 16:9 图的实际尺寸 +ORANGE = (255, 122, 0) +BLUE = (30, 107, 255) +GRAY = (150, 156, 166) +HEAD_FILL = (205, 209, 216) + +# 人偶比例(单位 = 站姿身高,原点 = 两脚中点的地面,y 向上) +HEAD_TOP, HEAD_R = 1.0, 0.085 +NECK = 0.80 +SH_Y, SH_HALF = 0.775, 0.105 +HIP_Y, HIP_HALF = 0.49, 0.062 +THIGH, SHIN = 0.235, 0.225 +UPPER_ARM, FOREARM, HAND = 0.155, 0.14, 0.05 +FOOT = 0.06 + + +def outward(length, deg_from_down, side): + """从竖直向下开始、向身体外侧转 deg 度的向量;side +1 = 被拍者左侧(画面右侧)""" + a = math.radians(deg_from_down) + return side * length * math.sin(a), -length * math.cos(a) + + +class Pose: + def __init__(self): + self.hip_drop = 0.0 + self.stance = 0.07 # 两脚间距的一半 + self.arm = {s: dict(abd=6.0, elbow=0.0, fwd=0.0) for s in (1, -1)} + self.leg = {s: dict(abd=0.0, lift=0.0) for s in (1, -1)} + + +def draw_pose(d: ImageDraw.ImageDraw, p: Pose, cx: float, ground: float, scale: float): + def P(x, y): + return cx + x * scale, ground - y * scale + + lw = max(4, int(scale * 0.028)) + + def joints(points, col, hand=None): + d.line([P(*q) for q in points], fill=col, width=lw, joint="curve") + for q in points[:-1]: + x, y = P(*q) + d.ellipse([x - lw * 0.7, y - lw * 0.7, x + lw * 0.7, y + lw * 0.7], fill=col) + if hand: + x, y = P(*hand) + d.ellipse([x - lw, y - lw, x + lw, y + lw], fill=col) + + hip_y, sh_y = HIP_Y - p.hip_drop, SH_Y - p.hip_drop + for s, col in ((1, ORANGE), (-1, BLUE)): + leg = p.leg[s] + hip = (s * HIP_HALF, hip_y) + if leg["lift"] > 0: + # 抬膝:大腿向前抬(正面投影变短),小腿自然下垂;膝盖高度随 lift 线性上升 + th = math.acos(1 - leg["lift"]) + knee = (hip[0] + s * 0.01, hip[1] - THIGH * math.cos(th)) + ankle = (knee[0] + s * 0.005, knee[1] - SHIN * 0.97) + else: + dx, dy = outward(THIGH + SHIN, leg["abd"], s) + ankle = (s * p.stance + dx, max(0.035, hip[1] + dy)) + bend = s * 0.012 * p.hip_drop / 0.05 if p.hip_drop else 0 + knee = ((hip[0] + ankle[0]) / 2 + bend, (hip[1] + ankle[1]) / 2 + 0.004) + joints([hip, knee, ankle, (ankle[0] + s * FOOT * 0.6, ankle[1] - 0.03)], col) + d.polygon([P(-SH_HALF, sh_y), P(SH_HALF, sh_y), P(HIP_HALF * 1.25, hip_y - 0.01), P(-HIP_HALF * 1.25, hip_y - 0.01)], fill=GRAY) + d.line([P(0, sh_y), P(0, NECK - p.hip_drop + 0.03)], fill=GRAY, width=int(lw * 1.6)) + hx, hy = P(0, HEAD_TOP - HEAD_R * 1.25 - p.hip_drop) + r = HEAD_R * scale + d.ellipse([hx - r * 0.86, hy - r, hx + r * 0.86, hy + r], fill=HEAD_FILL, outline=GRAY, width=2) + for s, col in ((1, ORANGE), (-1, BLUE)): + arm = p.arm[s] + sh = (s * SH_HALF, sh_y) + shrink = 1 - 0.75 * arm["fwd"] + ex, ey = outward(UPPER_ARM * shrink, arm["abd"], s) + elbow = (sh[0] + ex, sh[1] + ey) + if arm["elbow"] > 0: + # 屈肘前摆:前臂向身体内侧、向上折,正面看拳头抬到胸前 + bend = math.radians(arm["elbow"]) + wrist = (elbow[0] - s * FOREARM * math.sin(bend) * 0.55 + s * 0.01, elbow[1] - FOREARM * math.cos(bend)) + hand = (wrist[0] - s * 0.012, wrist[1] + 0.03 * math.sin(bend)) + else: + wx, wy = outward(FOREARM * shrink, arm["abd"], s) + wrist = (elbow[0] + wx, elbow[1] + wy) + hx2, hy2 = outward(HAND * shrink, arm["abd"], s) + hand = (wrist[0] + hx2, wrist[1] + hy2) + joints([sh, elbow, wrist, hand], col, hand=hand) + + +def jack(a: float, side: int) -> Pose: + """低冲击开合跳:双臂经体侧举过头顶成 V,side 侧腿向外点地,支撑腿微屈""" + p = Pose() + p.stance = 0.045 + for s in (1, -1): + p.arm[s] = dict(abd=8 + 157 * a, elbow=0, fwd=0.0) + p.leg[side]["abd"] = 26 * a + p.hip_drop = (THIGH + SHIN) * (1 - math.cos(math.radians(26 * a))) * 0.9 + return p + + +def raise_(a: float) -> Pose: + """站姿侧平举:只有手臂动,从体侧抬到与肩同高""" + p = Pose() + for s in (1, -1): + p.arm[s] = dict(abd=6 + 84 * a, elbow=0, fwd=0.0) + return p + + +def sheet(poses, rows, cols, fig_h, out): + img = Image.new("RGB", (W, H), "white") + d = ImageDraw.Draw(img) + cell_w, cell_h = W / cols, H / rows + for i, p in enumerate(poses): + r, c = divmod(i, cols) + draw_pose(d, p, cell_w * (c + 0.5), cell_h * (r + 1) - cell_h * 0.06, fig_h) + img.save(out) + + +GUIDES = { + # 动作: (帧数, 行, 列, 人偶身高, 幅度 → 姿势) + "raise": (18, 3, 6, 262, raise_), + "jack": (18, 3, 6, 262, lambda a: jack(a, 1)), +} + +if __name__ == "__main__": + move, out = sys.argv[1], sys.argv[2] + n, rows, cols, fig_h, pose_at = GUIDES[move] + sheet([pose_at(i / (n - 1)) for i in range(n)], rows, cols, fig_h, out) + print("已生成", out) diff --git a/training-videos/scripts/coach_vision.py b/training-videos/scripts/coach_vision.py new file mode 100644 index 000000000..38645d9ae --- /dev/null +++ b/training-videos/scripts/coach_vision.py @@ -0,0 +1,102 @@ +""" +教练帧处理用到的两个模型 + +- 关键点:torchvision Keypoint R-CNN(COCO 17 点),用来对齐、统一比例、测量每帧的动作幅度 +- 超分:Real-ESRGAN x4plus(经 spandrel 加载),把设定稿里 300~450px 高的人物放大后再缩到主帧尺寸, + 边缘和五官比直接插值放大清晰得多 + +模型权重首次使用时自动下载:Keypoint R-CNN 由 torchvision 缓存到 TORCH_HOME, +Real-ESRGAN 下载到 training-videos/work/models。 +""" +import math +import urllib.request +from pathlib import Path + +import cv2 +import numpy as np +import torch +import torchvision + +ROOT = Path(__file__).resolve().parents[1] +MODEL_DIR = ROOT / "work" / "models" +SR_URL = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth" + +KP_NAMES = ["nose", "leye", "reye", "lear", "rear", "lsh", "rsh", "lel", "rel", "lwr", "rwr", "lhip", "rhip", "lkn", "rkn", "lan", "ran"] +BG = (240, 244, 248) + +_kp_model = None +_sr_model = None + + +def _device(): + return torch.device("mps" if torch.backends.mps.is_available() else "cpu") + + +def on_background(rgba: np.ndarray) -> np.ndarray: + a = rgba[..., 3:4].astype(np.float32) / 255 + return (rgba[..., :3] * a + np.array(BG, np.float32) * (1 - a)).round().astype(np.uint8) + + +def keypoints(rgba: np.ndarray) -> dict: + """人物关键点 {名称: (x, y, 置信度)};被拍者的左侧(l*)在画面右侧""" + global _kp_model + if _kp_model is None: + _kp_model = torchvision.models.detection.keypointrcnn_resnet50_fpn(weights="DEFAULT").eval() + x = torch.from_numpy(on_background(rgba)).permute(2, 0, 1).float() / 255 + with torch.no_grad(): + out = _kp_model([x])[0] + if len(out["scores"]) == 0: + raise ValueError("没有检测到人物") + i = int(out["scores"].argmax()) + k, s = out["keypoints"][i].numpy(), out["keypoints_scores"][i].numpy() + return {n: (float(k[j, 0]), float(k[j, 1]), float(s[j])) for j, n in enumerate(KP_NAMES)} + + +def arm_angle(kp: dict, side: str) -> float: + """手臂外展角:0 = 竖直向下,90 = 水平,180 = 竖直向上""" + sh, wr = kp[f"{side}sh"], kp[f"{side}wr"] + return math.degrees(math.atan2(abs(wr[0] - sh[0]), wr[1] - sh[1])) + + +def shoulder_center(kp: dict): + return (kp["lsh"][0] + kp["rsh"][0]) / 2, (kp["lsh"][1] + kp["rsh"][1]) / 2 + + +def _sr(): + global _sr_model + if _sr_model is None: + from spandrel import ModelLoader + + path = MODEL_DIR / Path(SR_URL).name + if not path.exists(): + MODEL_DIR.mkdir(parents=True, exist_ok=True) + print(f"下载 {path.name} …") + urllib.request.urlretrieve(SR_URL, path) + _sr_model = ModelLoader().load_from_file(str(path)).model.eval().to(_device()) + return _sr_model + + +def _run_sr(rgb: np.ndarray) -> np.ndarray: + x = torch.from_numpy(rgb.astype(np.float32) / 255).permute(2, 0, 1)[None].to(_device()) + with torch.no_grad(): + y = _sr()(x) + return (y[0].permute(1, 2, 0).clamp(0, 1).cpu().numpy() * 255).round().astype(np.uint8) + + +def _bleed(rgba: np.ndarray) -> np.ndarray: + """把前景颜色向透明区域外扩,放大时边缘不会混进背景色(黑边 / 灰边)""" + hole = (rgba[..., 3] < 16).astype(np.uint8) + rgb = rgba[..., :3].copy() + small = cv2.resize(rgb, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA) + small_hole = cv2.resize(hole, (small.shape[1], small.shape[0]), interpolation=cv2.INTER_NEAREST) + filled = cv2.resize(cv2.inpaint(small, small_hole, 3, cv2.INPAINT_TELEA), (rgb.shape[1], rgb.shape[0])) + rgb[hole > 0] = filled[hole > 0] + return rgb + + +def upscale4(rgba: np.ndarray) -> np.ndarray: + """透明图 4 倍超分:颜色与 alpha 分别放大""" + rgb = _run_sr(_bleed(rgba)) + a = rgba[..., 3] + alpha = _run_sr(np.dstack([a, a, a]))[..., 0] + return np.dstack([rgb, alpha]) diff --git a/training-videos/scripts/gen-sheets.sh b/training-videos/scripts/gen-sheets.sh new file mode 100755 index 000000000..c77fdf204 --- /dev/null +++ b/training-videos/scripts/gen-sheets.sh @@ -0,0 +1,38 @@ +#!/bin/zsh +# 用本机 Codex(image_generation)生成连续动作设定稿:一张图里按阅读顺序画一个动作的全部帧 +# +# 用法:scripts/gen-sheets.sh [动作 ...] (默认 squat raise jack march,并行生成) +# 产物:work/sheets/<动作>.png(透明背景 16:9,约 1672×941),日志 work/sheets/log_<动作>.txt +# 挑出满意的结果后:python3 scripts/coach_adopt.py <动作>,收进 assets/sheets 再跑 coach_frames.py +# +# 参考图:assets/coach-reference.png(角色)+ 第二张姿势参考: +# squat / march:起止关键姿势(assets/sheets/refs/<动作>_keys.jpg) +# raise / jack :程序画的逐帧人偶姿势图(coach_pose_guide.py),帧间距和左右都由程序决定 +# 依赖:codex CLI 已登录,且开启 image_generation 功能(codex features list 可查) +set -u +ROOT=${0:a:h:h} +OUT=$ROOT/work/sheets +mkdir -p $OUT +REF=$ROOT/assets/coach-reference.png + +gen_one() { + local move=$1 pose + case $move in + squat|march) pose=$ROOT/assets/sheets/refs/${move}_keys.jpg ;; + raise|jack) + pose=$OUT/guide_$move.png + python3 $ROOT/scripts/coach_pose_guide.py $move $pose > /dev/null ;; + *) echo "未知动作:$move"; return 1 ;; + esac + rm -f $OUT/$move.png + # -i 可以接多个文件,提示词前必须加 --,否则会被当成图片路径 + codex exec --skip-git-repo-check --ephemeral -s workspace-write -C $OUT -i "$REF,$pose" -- "$(cat $ROOT/scripts/prompts/$move.txt)" \ + < /dev/null > $OUT/log_$move.txt 2>&1 + echo "$move: $(file -b $OUT/$move.png 2>/dev/null || echo 失败,见 log_$move.txt)" +} + +for move in ${@:-squat raise jack march}; do + gen_one $move & + sleep 1 +done +wait diff --git a/training-videos/scripts/prompts/jack.txt b/training-videos/scripts/prompts/jack.txt new file mode 100644 index 000000000..c85dc1512 --- /dev/null +++ b/training-videos/scripts/prompts/jack.txt @@ -0,0 +1,4 @@ +Use your image generation tool to create ONE wide 16:9 landscape image with a TRANSPARENT background (PNG with a real alpha channel). Do not resize, upscale or edit the generated image afterwards. The first attached image is the exact character reference. The second attached image is a POSE GUIDE: 18 simple mannequins on a white background, laid out in three rows of six. In the guide, ORANGE limbs are the character's LEFT arm and LEFT leg, BLUE limbs are her RIGHT arm and RIGHT leg; the mannequins face the viewer, so her left side appears on the RIGHT side of the image. After generating, copy the PNG into the current working directory as jack.png, then print its pixel size and whether it has an alpha channel. Do not do anything else. + +Image prompt: Turn the pose guide into a finished professional 2D animation sprite sheet for a fitness app. Replace every mannequin with the exact character from the reference image, posed EXACTLY like that mannequin (same arm angles, same elbow bends, same knee heights, same leg positions, the same limb raised or stepped out on the same side of the image), at exactly the same position and size in the layout, while keeping the character's own natural proportions (her head, face, hair and slim athletic body). The 18 figures are 18 consecutive animation frames of ONE low-impact jumping jack with no jumping: both straight arms sweep out and up to a V over her head in small even steps while her LEFT leg (on the RIGHT side of the image) gradually taps out to the side and her RIGHT leg stays planted, read like text (left to right, row by row), so each figure must differ from its neighbour only as much as their mannequins differ: small, even, smooth steps. Every figure at exactly the same scale (same head size), front view facing the viewer, whole body visible from head to toe, feet on each row's shared invisible ground line, clear empty space between figures. Natural, coordinated, human-like movement like a real fitness coach, with the same friendly smile in every frame. Do not draw the mannequins themselves, and none of their grey, orange or blue colors, lines or dots: only the character. +Same woman, same face, same high ponytail, same mint green short-sleeve athletic t-shirt, same dark green-gray full-length leggings, same white sneakers with mint accents, same stylized Pixar-like 3D cartoon rendering, same soft studio lighting in every frame. Transparent background, no floor, no shadows, no text, no numbers, no labels, no arrows, no grid lines. diff --git a/training-videos/scripts/prompts/march.txt b/training-videos/scripts/prompts/march.txt new file mode 100644 index 000000000..454fee5d5 --- /dev/null +++ b/training-videos/scripts/prompts/march.txt @@ -0,0 +1,7 @@ +Use your image generation tool to create ONE wide 16:9 landscape image with a TRANSPARENT background (PNG with a real alpha channel). Do not resize, upscale or edit the generated image afterwards. The first attached image is the exact character reference. The second attached image shows three key poses: LEFT figure = the standing start pose, MIDDLE figure = the top of the LEFT-knee lift, RIGHT figure = the top of the RIGHT-knee lift. After generating, copy the PNG into the current working directory as march.png, then print its pixel size and whether it has an alpha channel. Do not do anything else. + +Image prompt: A professional 2D animation in-between sprite sheet for a fitness app. The exact same character as the reference image is drawn 18 times as two 9-frame animation sequences of marching in place with high knees, arranged in two rows of nine. Every figure is drawn at exactly the same scale (same head size in every frame), front view facing the viewer, whole body visible from head to toe, each row standing on one shared invisible ground line, the standing foot planted at the same spot within each figure's own cell, clear empty space between figures, no figure touching or overlapping another. +TOP ROW, frames 1 to 9 from left to right: frame 1 is exactly the standing start pose (feet hip-width apart, arms relaxed at her sides, gentle smile). Frames 2 to 9 are evenly spaced in-betweens of ONE smooth knee lift: her LEFT knee (on the RIGHT side of the image) rises a little more in every frame (strictly monotonic, never lower than the previous frame): first the heel peels off the floor, then the knee lifts forward and up until frame 9 = her left thigh parallel to the floor at hip height with the foot hanging below the knee (exactly the MIDDLE key pose). In sync with the knee, her RIGHT arm (on the LEFT side of the image) swings forward and up with the elbow bending to 90 degrees so the relaxed fist comes up to chest height, while her LEFT arm (RIGHT side of the image) swings slightly back beside her hip. +BOTTOM ROW, frames 10 to 18 from left to right: the same movement with the other leg. Frame 10 is the same standing start pose as frame 1. Frames 11 to 18: her RIGHT knee (on the LEFT side of the image) rises a little more in every frame until frame 18 = her right thigh parallel to the floor (exactly the RIGHT key pose), while her LEFT arm (RIGHT side of the image) swings forward and up with the elbow bent to 90 degrees, fist at chest height, and her RIGHT arm swings slightly back. +The standing leg stays straight and firmly planted, the torso stays tall and upright, the head stays level. Natural, coordinated, springy, human-like movement like a real fitness coach, with the same cheerful smile in every frame. +Same woman, same face, same high ponytail, same mint green short-sleeve athletic t-shirt, same dark green-gray full-length leggings, same white sneakers with mint accents, same stylized Pixar-like 3D cartoon rendering, same soft studio lighting in every frame. Transparent background, no floor, no shadows, no text, no numbers, no labels, no arrows, no grid lines. diff --git a/training-videos/scripts/prompts/raise.txt b/training-videos/scripts/prompts/raise.txt new file mode 100644 index 000000000..292aa79fc --- /dev/null +++ b/training-videos/scripts/prompts/raise.txt @@ -0,0 +1,4 @@ +Use your image generation tool to create ONE wide 16:9 landscape image with a TRANSPARENT background (PNG with a real alpha channel). Do not resize, upscale or edit the generated image afterwards. The first attached image is the exact character reference. The second attached image is a POSE GUIDE: 18 simple mannequins on a white background, laid out in three rows of six. In the guide, ORANGE limbs are the character's LEFT arm and LEFT leg, BLUE limbs are her RIGHT arm and RIGHT leg; the mannequins face the viewer, so her left side appears on the RIGHT side of the image. After generating, copy the PNG into the current working directory as raise.png, then print its pixel size and whether it has an alpha channel. Do not do anything else. + +Image prompt: Turn the pose guide into a finished professional 2D animation sprite sheet for a fitness app. Replace every mannequin with the exact character from the reference image, posed EXACTLY like that mannequin (same arm angles, same elbow bends, same knee heights, same leg positions, the same limb raised or stepped out on the same side of the image), at exactly the same position and size in the layout, while keeping the character's own natural proportions (her head, face, hair and slim athletic body). The 18 figures are consecutive animation frames of a slow standing lateral raise (both straight arms rise out to the sides from hanging down to shoulder height; only the arms move), read like text (left to right, row by row), so each figure must differ from its neighbour only as much as their mannequins differ: small, even, smooth steps. Every figure at exactly the same scale (same head size), front view facing the viewer, whole body visible from head to toe, feet on each row's shared invisible ground line, clear empty space between figures. Natural, coordinated, human-like movement like a real fitness coach, with the same friendly smile in every frame. Do not draw the mannequins themselves, and none of their grey, orange or blue colors, lines or dots: only the character. +Same woman, same face, same high ponytail, same mint green short-sleeve athletic t-shirt, same dark green-gray full-length leggings, same white sneakers with mint accents, same stylized Pixar-like 3D cartoon rendering, same soft studio lighting in every frame. Transparent background, no floor, no shadows, no text, no numbers, no labels, no arrows, no grid lines. diff --git a/training-videos/scripts/prompts/squat.txt b/training-videos/scripts/prompts/squat.txt new file mode 100644 index 000000000..091f4003c --- /dev/null +++ b/training-videos/scripts/prompts/squat.txt @@ -0,0 +1,5 @@ +Use your image generation tool to create ONE wide 16:9 landscape image with a TRANSPARENT background (PNG with a real alpha channel). Do not resize, upscale or edit the generated image afterwards. The first attached image is the exact character reference. The second attached image shows the START pose (left figure) and the END pose (right figure) of the motion. After generating, copy the PNG into the current working directory as squat.png, then print its pixel size and whether it has an alpha channel. Do not do anything else. + +Image prompt: A professional 2D animation in-between sprite sheet for a fitness app. The exact same character as the reference image is drawn 18 times as 18 consecutive animation frames of ONE smooth, natural bodyweight squat descent, arranged in two rows of nine: top row = frames 1 to 9 from left to right, bottom row = frames 10 to 18 from left to right. Every figure is drawn at exactly the same scale (same head size in every frame), front view facing the viewer, whole body visible from head to toe, each row standing on one shared invisible ground line, feet planted at the same spot within each figure's own cell, generous empty space between figures, no figure touching or overlapping another. +Frame 1 is exactly the START pose: standing upright, feet slightly wider than shoulder width with toes turned slightly out, arms relaxed at her sides, gentle smile. Frame 18 is exactly the END pose: bottom of a correct squat, thighs parallel to the floor, knees pushed out over the toes, chest up, both arms straight forward at shoulder height. Frames 2 to 17 are evenly spaced in-betweens: each frame goes only a little lower than the previous one (the descent is strictly monotonic, every frame slightly lower than the one before, never higher), the hips move back and down smoothly, the knees bend gradually and track over the toes, heels stay flat, the chest stays up with a slight natural forward lean, and both straight arms rise forward smoothly in sync with the depth (from hanging at her sides in frame 1 to shoulder height in frame 18). Natural, coordinated, human-like movement like a real fitness coach, with the same calm focused smile in every frame. +Same woman, same face, same high ponytail, same mint green short-sleeve athletic t-shirt, same dark green-gray full-length leggings, same white sneakers with mint accents, same stylized Pixar-like 3D cartoon rendering, same soft studio lighting in every frame. Transparent background, no floor, no shadows, no text, no numbers, no labels, no arrows, no grid lines. diff --git a/training-videos/scripts/render-all.mjs b/training-videos/scripts/render-all.mjs new file mode 100644 index 000000000..54cb7266f --- /dev/null +++ b/training-videos/scripts/render-all.mjs @@ -0,0 +1,9 @@ +// 渲染全部示范视频到 out/*.mp4(可指定单个动作:node scripts/render-all.mjs squat) +import { execSync } from 'node:child_process' + +const MOVES = ['squat', 'march', 'jack', 'raise'] +const only = process.argv[2] + +for (const move of MOVES.filter((m) => !only || m === only)) { + execSync(`npx remotion render src/index.ts demo-${move} out/${move}.mp4`, { stdio: 'inherit' }) +} diff --git a/training-videos/scripts/requirements.txt b/training-videos/scripts/requirements.txt new file mode 100644 index 000000000..3fa6fb089 --- /dev/null +++ b/training-videos/scripts/requirements.txt @@ -0,0 +1,8 @@ +# 教练帧流水线(coach_frames.py / coach_export.py)的 Python 依赖 +# 安装:python3 -m venv .venv && .venv/bin/pip install -r scripts/requirements.txt +numpy +opencv-python-headless +pillow +torch +torchvision +spandrel diff --git a/training-videos/scripts/serve.mjs b/training-videos/scripts/serve.mjs new file mode 100644 index 000000000..4ef107115 --- /dev/null +++ b/training-videos/scripts/serve.mjs @@ -0,0 +1,38 @@ +// 本地联调用静态服务(小程序开发者工具访问):/squat.mp4 → out/ 下渲染好的示范视频(支持 Range 拖动进度), +// /coach/sq_00.webp → out/coach/ 下的教练高清帧,/music/calm.mp3 → out/music/ 下的背景音乐 +import { createServer } from 'node:http' +import { createReadStream, statSync, existsSync } from 'node:fs' +import { join, normalize, extname } from 'node:path' + +const PORT = Number(process.env.PORT || 5179) +const ROOT = join(import.meta.dirname, '..') +const TYPES = { '.mp4': 'video/mp4', '.webp': 'image/webp', '.mp3': 'audio/mpeg' } + +function resolve(url) { + const name = normalize(decodeURIComponent((url || '/').split('?')[0])).replace(/^([/\\.])+/, '') + const base = join(ROOT, 'out') + const file = join(base, name) + const type = TYPES[extname(file)] + if (!file.startsWith(base) || !type || !existsSync(file)) return null + return { file, type } +} + +createServer((req, res) => { + const hit = resolve(req.url) + if (!hit) { + res.writeHead(404).end('not found') + return + } + const size = statSync(hit.file).size + const headers = { 'Content-Type': hit.type, 'Accept-Ranges': 'bytes', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'max-age=60' } + const range = /bytes=(\d*)-(\d*)/.exec(req.headers.range || '') + if (range) { + const start = range[1] ? Number(range[1]) : 0 + const end = range[2] ? Math.min(Number(range[2]), size - 1) : size - 1 + res.writeHead(206, { ...headers, 'Content-Range': `bytes ${start}-${end}/${size}`, 'Content-Length': end - start + 1 }) + createReadStream(hit.file, { start, end }).pipe(res) + return + } + res.writeHead(200, { ...headers, 'Content-Length': size }) + createReadStream(hit.file).pipe(res) +}).listen(PORT, process.env.HOST || '0.0.0.0', () => console.log(`示范视频服务已启动: http://127.0.0.1:${PORT}/squat.mp4`)) diff --git a/training-videos/src/CoachFrames.tsx b/training-videos/src/CoachFrames.tsx new file mode 100644 index 000000000..129800153 --- /dev/null +++ b/training-videos/src/CoachFrames.tsx @@ -0,0 +1,51 @@ +import React from 'react' +import { Img, staticFile, useCurrentFrame, useVideoConfig } from 'remotion' +import { MOTIONS, type MoveId } from '../../TUICallKit-Vue3/training/pose/motions.ts' +import { crispBlend, framePair } from '../../TUICallKit-Vue3/training/pose/coachFrames.ts' + +const src = (name: string) => staticFile(`coach/hd/${name}.webp`) + +type Props = { size: number; style?: React.CSSProperties } + +/** 单帧教练(手势、错误示范等静态姿势),带轻微呼吸起伏 */ +export const CoachPose: React.FC = ({ name, size, bob = true, style }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const y = bob ? Math.sin((f / fps) * 2.2) * size * 0.004 : 0 + return +} + +/** + * 按示范节奏播放的教练动画:动作幅度 → 连续帧序列中的小数位置, + * 以清晰单帧为主、换帧瞬间短暂过渡(与小程序 coach-sprite 一致)。 + */ +export const CoachMotion: React.FC = ({ move, size, speed = 1, style }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const spec = MOTIONS[move] + const a = spec.amountAt(((f / fps) * speed) / spec.cycleSeconds) + const pair = framePair(move, a.amount, a.side) + const w = crispBlend(pair.w) + const layer: React.CSSProperties = { position: 'absolute', left: 0, top: 0, width: size, height: size } + return ( +
+ {w < 0.999 ? : null} + {w > 0.001 ? : null} +
+ ) +} + +/** 地面柔和阴影 */ +export const GroundShadow: React.FC<{ size: number }> = ({ size }) => ( +
+) diff --git a/training-videos/src/DemoVideo.tsx b/training-videos/src/DemoVideo.tsx new file mode 100644 index 000000000..532f8f0cf --- /dev/null +++ b/training-videos/src/DemoVideo.tsx @@ -0,0 +1,268 @@ +import React from 'react' +import { AbsoluteFill, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion' +import { MOTIONS, type MoveId } from '../../TUICallKit-Vue3/training/pose/motions.ts' +import { EXERCISES } from '../../TUICallKit-Vue3/training/pose/exercises.ts' +import { MISTAKE_FRAMES, peakFrame } from '../../TUICallKit-Vue3/training/pose/coachFrames.ts' +import { CoachMotion, CoachPose, GroundShadow } from './CoachFrames' +import { FONT, INK, MUTED, darken, tint } from './theme' + +export type DemoProps = { move: MoveId } + +/** “错误 vs 正确”卡片里人物的放大与裁切(不同动作人物高度不同) */ +const MISTAKE_VIEW: Record = { + squat: { size: 880, top: -330 }, + march: { size: 700, top: -150 }, + jack: { size: 640, top: -60 }, + raise: { size: 640, top: -110 }, +} + +/** 各场景时长(帧,30fps) */ +export const SCENES = { intro: 75, demo: 270, keys: 210, mistake: 150, outro: 90 } +export const DEMO_DURATION = Object.values(SCENES).reduce((a, b) => a + b, 0) + +function useFade(total: number) { + const f = useCurrentFrame() + return Math.min( + interpolate(f, [0, 12], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }), + interpolate(f, [total - 10, total], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }), + ) +} + +const Background: React.FC<{ color: string }> = ({ color }) => { + const f = useCurrentFrame() + const drift = Math.sin(f / 60) * 40 + return ( + +
+ + ) +} + +const TopBar: React.FC<{ name: string; color: string }> = ({ name, color }) => ( +
+
+
+ AI 跟练 · 小甄教练 +
+ {name} +
+) + +const Intro: React.FC<{ move: MoveId }> = ({ move }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const ex = EXERCISES[move] + const s = spring({ frame: f, fps, config: { damping: 14 } }) + const coachIn = spring({ frame: f - 8, fps, config: { damping: 16 } }) + return ( + +
+
{ex.name}
+
{ex.tagline}
+
+ {ex.targets.map((t) => ( + + {t} + + ))} +
+
+
+ + +
+
+ ) +} + +const Demo: React.FC<{ move: MoveId }> = ({ move }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const ex = EXERCISES[move] + const spec = MOTIONS[move] + const phase = f / fps / spec.cycleSeconds + const rep = Math.floor(phase * spec.repsPerCycle) + 1 + const fade = useFade(SCENES.demo) + return ( + + +
+
示范
+
+ {rep} + 次 +
+
+
+ + +
+
+
+ {spec.phaseLabel(phase)} +
+
+
+
+
+ + ) +} + +const KeyPoints: React.FC<{ move: MoveId }> = ({ move }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const ex = EXERCISES[move] + const fade = useFade(SCENES.keys) + return ( + + +
+ +
+
+
动作要领
+ {ex.keyPoints.map((k, i) => { + const s = spring({ frame: f - 18 - i * 30, fps, config: { damping: 16 } }) + return ( +
+
+ {i + 1} +
+
{k}
+
+ ) + })} +
+
+ ) +} + +const Mistake: React.FC<{ move: MoveId }> = ({ move }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const ex = EXERCISES[move] + const fade = useFade(SCENES.mistake) + const correct = peakFrame(move) + const view = MISTAKE_VIEW[move] + const panel = (ok: boolean, i: number) => { + const c = ok ? '#10b981' : '#ef4444' + const s = spring({ frame: f - 6 - i * 10, fps, config: { damping: 15 } }) + return ( +
+
{ok ? '✓' : '✕'}
+
+
+ +
+
+
{ok ? ex.mistake.right : ex.mistake.wrong}
+
+ ) + } + return ( + + +
错误 vs 正确
+
+ {panel(false, 0)} + {panel(true, 1)} +
+
跟练时 AI 会实时提醒你
+
+ ) +} + +const Outro: React.FC<{ move: MoveId }> = ({ move }) => { + const f = useCurrentFrame() + const { fps } = useVideoConfig() + const ex = EXERCISES[move] + const s = spring({ frame: f, fps, config: { damping: 14 } }) + const hop = Math.abs(Math.sin((f / fps) * 5)) * 18 + const corner = (style: React.CSSProperties) =>
+ return ( + +
+ {corner({ left: 0, top: 0, borderLeftWidth: 12, borderTopWidth: 12, borderTopLeftRadius: 40 })} + {corner({ right: 0, top: 0, borderRightWidth: 12, borderTopWidth: 12, borderTopRightRadius: 40 })} + {corner({ left: 0, bottom: 0, borderLeftWidth: 12, borderBottomWidth: 12, borderBottomLeftRadius: 40 })} + {corner({ right: 0, bottom: 0, borderRightWidth: 12, borderBottomWidth: 12, borderBottomRightRadius: 40 })} +
+ +
+
+
+
站到镜头前,一起练
+
AI 自动数次数 · 实时纠正动作
+
+
+ ) +} + +export const DemoVideo: React.FC = ({ move }) => { + const ex = EXERCISES[move] + let at = 0 + const seq = (d: number) => { + const from = at + at += d + return { from, durationInFrames: d } + } + return ( + + + + + + + + + + + + + + + + + + + ) +} diff --git a/training-videos/src/Root.tsx b/training-videos/src/Root.tsx new file mode 100644 index 000000000..d7df9b4be --- /dev/null +++ b/training-videos/src/Root.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { Composition } from 'remotion' +import { EXERCISE_ORDER } from '../../TUICallKit-Vue3/training/pose/exercises.ts' +import { DemoVideo, DEMO_DURATION } from './DemoVideo' +import { Showcase } from './Showcase' + +export const Root: React.FC = () => ( + <> + + {EXERCISE_ORDER.map((move) => ( + + ))} + +) diff --git a/training-videos/src/Showcase.tsx b/training-videos/src/Showcase.tsx new file mode 100644 index 000000000..759aeddf2 --- /dev/null +++ b/training-videos/src/Showcase.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { AbsoluteFill } from 'remotion' +import { EXERCISE_ORDER, EXERCISES } from '../../TUICallKit-Vue3/training/pose/exercises.ts' +import { CoachMotion, GroundShadow } from './CoachFrames' +import { FONT, INK, tint } from './theme' + +/** 四个动作并排循环,用于检查教练动作的连贯性 */ +export const Showcase: React.FC = () => ( + + {EXERCISE_ORDER.map((move) => { + const ex = EXERCISES[move] + return ( +
+
+ + +
+
{ex.name}
+
+ ) + })} +
+) diff --git a/training-videos/src/index.ts b/training-videos/src/index.ts new file mode 100644 index 000000000..6f15f4d79 --- /dev/null +++ b/training-videos/src/index.ts @@ -0,0 +1,4 @@ +import { registerRoot } from 'remotion' +import { Root } from './Root' + +registerRoot(Root) diff --git a/training-videos/src/theme.ts b/training-videos/src/theme.ts new file mode 100644 index 000000000..a8b6292e0 --- /dev/null +++ b/training-videos/src/theme.ts @@ -0,0 +1,22 @@ +export const FONT = '"PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif' +export const INK = '#0f172a' +export const MUTED = '#64748b' + +function parse(hex: string): [number, number, number] { + const h = hex.replace('#', '') + return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number] +} + +function toHex(rgb: number[]): string { + return '#' + rgb.map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')).join('') +} + +/** 与白色混合,amount 越大越浅 */ +export function tint(hex: string, amount: number): string { + return toHex(parse(hex).map((v) => v + (255 - v) * amount)) +} + +/** 与黑色混合,amount 越大越深 */ +export function darken(hex: string, amount: number): string { + return toHex(parse(hex).map((v) => v * (1 - amount))) +} diff --git a/training-videos/tsconfig.json b/training-videos/tsconfig.json new file mode 100644 index 000000000..547fc28aa --- /dev/null +++ b/training-videos/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "lib": ["DOM", "ES2020"] + }, + "include": ["src"] +}