Files
xiaoxia-saas/scripts/upload_preset_bgm.py
T
Coze Agent ca6803e1a5
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 46s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m4s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m18s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m56s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m20s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m29s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m31s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m0s
AI Code Review / AI Code Review (pull_request) Successful in 6m50s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 10m50s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 0s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m36s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 30s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m3s
chore(worker): move bgm upload readme under docs/ to avoid ruff parsing md
2026-09-29 15:35:34 +08:00

155 lines
6.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""一键上传预设 BGM 到 OSS 并回填 preset_bgm.py audio_url。
用法(在服务器或本地有 OSS 凭证的机器上执行):
1. 把 mp3 文件放到 ./bgm_assets/ (created by you) 目录下,文件名按 {preset_id}.mp3 命名:
bgm_upbeat_001.mp3 阳光清晨
bgm_upbeat_002.mp3 活力节拍
bgm_upbeat_003.mp3 夏日漫步
bgm_relax_001.mp3 静谧时光
bgm_relax_002.mp3 雨后森林
bgm_relax_003.mp3 月光奏鸣曲
bgm_tech_001.mp3 未来科技
bgm_tech_002.mp3 数据脉冲
bgm_commerce_001.mp3 心动时刻
bgm_commerce_002.mp3 品质生活
2. 确保环境变量已设置:
OSS_ENDPOINT, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET_NAME
3. 运行:python scripts/upload_preset_bgm.py
4. 脚本会上传到 OSS 路径 preset/bgm/<id>.mp3,并自动改写
packages/domain/preset_bgm.py 填入 public URL。
5. git commit & push 即可。
支持可选参数:
--bgm-dir DIR 本地 mp3 目录(默认 ./bgm_assets)
--oss-prefix PFX OSS key 前缀(默认 preset/bgm/)
--dry-run 只打印要做的操作,不上传不改写
--public 上传后设置公共读 ACL(默认开启,safe_download 走公网 URL)
"""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
REQUIRED_ENV = ("OSS_ENDPOINT", "OSS_ACCESS_KEY_ID", "OSS_ACCESS_KEY_SECRET", "OSS_BUCKET_NAME")
PRESET_BGM_PY = Path(__file__).resolve().parents[1] / "packages" / "domain" / "preset_bgm.py"
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--bgm-dir", default="./bgm_assets")
ap.add_argument("--oss-prefix", default="preset/bgm/")
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--public", action="store_true", default=True)
args = ap.parse_args()
bgm_dir = Path(args.bgm_dir)
if not bgm_dir.exists():
print(f"[ERR] 目录不存在: {bgm_dir}", file=sys.stderr)
return 2
# 加载 preset_bgm.py 中所有 ID(简单 AST 抽取)
sys.path.insert(0, str(PRESET_BGM_PY.parents[2]))
from packages.domain.preset_bgm import PRESET_BGM_LIBRARY # type: ignore
id_to_preset = {p.id: p for p in PRESET_BGM_LIBRARY}
# 枚举本地文件
local_files: dict[str, Path] = {}
for ext in (".mp3", ".m4a", ".aac", ".wav", ".ogg"):
for f in bgm_dir.glob(f"*{ext}"):
pid = f.stem
local_files[pid] = f
missing_local = [pid for pid in id_to_preset if pid not in local_files]
unknown_local = [pid for pid in local_files if pid not in id_to_preset]
print(f"[INFO] 预设 BGM 总数: {len(PRESET_BGM_LIBRARY)}")
print(f"[INFO] 本地找到对应文件: {len(local_files) - len(unknown_local)}")
if missing_local:
print(f"[WARN] 缺少本地音频文件的预设({len(missing_local)}):")
for pid in missing_local:
print(f" {pid} {id_to_preset[pid].name}")
if unknown_local:
print(f"[WARN] 本地文件未匹配任何 preset_id({len(unknown_local)}): {unknown_local}")
if args.dry_run:
for pid, f in local_files.items():
if pid in id_to_preset:
url = f"https://{os.environ.get('OSS_BUCKET_NAME', '<bucket>')}.{os.environ.get('OSS_ENDPOINT', '<ep>')}/{args.oss_prefix}{f.name}"
print(f"[DRY] would upload {f} → {args.oss_prefix}{f.name} → {url}")
return 0
# 凭证检查
for k in REQUIRED_ENV:
if not os.environ.get(k):
print(f"[ERR] 缺少环境变量 {k}", file=sys.stderr)
return 3
try:
import oss2 # type: ignore
except ImportError:
print("[ERR] 需要 oss2: pip install oss2", file=sys.stderr)
return 4
endpoint = os.environ["OSS_ENDPOINT"]
bucket_name = os.environ["OSS_BUCKET_NAME"]
auth = oss2.Auth(os.environ["OSS_ACCESS_KEY_ID"], os.environ["OSS_ACCESS_KEY_SECRET"])
bucket = oss2.Bucket(auth, f"https://{endpoint}", bucket_name)
public_base = f"https://{bucket_name}.{endpoint}"
pid_to_url: dict[str, str] = {}
for pid, f in local_files.items():
if pid not in id_to_preset:
continue
key = f"{args.oss_prefix.rstrip('/')}/{f.name}"
print(f"[UPLOAD] {f} → oss://{bucket_name}/{key}")
headers = {"Content-Type": "audio/mpeg" if f.suffix == ".mp3" else "audio/mp4"}
if args.public:
bucket.put_object_from_file(key, str(f), headers=headers)
bucket.put_object_acl(key, oss2.OBJECT_ACL_PUBLIC_READ)
else:
bucket.put_object_from_file(key, str(f), headers=headers)
url = f"{public_base}/{key}"
pid_to_url[pid] = url
print(f" → {url}")
if not pid_to_url:
print("[WARN] 没有文件被上传,不修改 preset_bgm.py")
return 0
# 回填 preset_bgm.py(按 id 精确替换 audio_url="" 为 audio_url="<url>")
text = PRESET_BGM_PY.read_text(encoding="utf-8")
orig = text
for pid, url in pid_to_url.items():
# 匹配 PresetBGM( ... id="pid", ... audio_url="", ... )
# 简单替换:找到 id="pid" 行开始的 PresetBGM 构造块,把块内的 audio_url="" 替换
import re
block_pat = re.compile(
r'(PresetBGM\([^)]*?id="' + re.escape(pid) + r'"[^)]*?audio_url=)"[^"]*"',
re.DOTALL,
)
new_text, n = block_pat.subn(rf'\1"{url}"', text, count=1)
if n == 0:
# 兜底:可能 audio_url 后面直接是 ),没赋值?不会,dataclass 有默认值
print(f"[WARN] 未在 PresetBGM 块中找到 id={pid} 的 audio_url 字段,跳过回填")
continue
text = new_text
if text != orig:
PRESET_BGM_PY.write_text(text, encoding="utf-8")
print(f"[OK] 已回填 {len(pid_to_url)} 条 audio_url 到 {PRESET_BGM_PY}")
print(
"[NEXT] git diff packages/domain/preset_bgm.py && git add -A && git commit -m 'feat(domain): fill preset BGM audio_urls' && git push"
)
else:
print("[WARN] preset_bgm.py 未发生变化(可能已填过)")
return 0
if __name__ == "__main__":
sys.exit(main())