Merge pull request 'chore(worker): BGM preset diagnostics + upload helper' (#2094) from chore/preset-bgm-audio-upload-helper into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 (push) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) 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 / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (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 / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
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 / PR Build API Image (pull_request) Successful in 1m6s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m4s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m43s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m5s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m19s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m3s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m22s
CI/CD Pipeline / Integration Tests (push) Successful in 4m21s
CI/CD Pipeline / Validate - Style (push) Successful in 5m16s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m44s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m32s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m11s
AI Code Review / AI Code Review (pull_request) Successful in 6m44s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m47s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m46s
CI/CD Pipeline / Validate - Security (push) Successful in 12m45s
CI/CD Pipeline / Unit Tests (push) Successful in 16m43s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 (push) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) 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 / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (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 / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
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 / PR Build API Image (pull_request) Successful in 1m6s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m4s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m43s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m5s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m19s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m3s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m22s
CI/CD Pipeline / Integration Tests (push) Successful in 4m21s
CI/CD Pipeline / Validate - Style (push) Successful in 5m16s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m44s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m32s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m11s
AI Code Review / AI Code Review (pull_request) Successful in 6m44s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m47s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m46s
CI/CD Pipeline / Validate - Security (push) Successful in 12m45s
CI/CD Pipeline / Unit Tests (push) Successful in 16m43s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
This commit was merged in pull request #2094.
This commit is contained in:
@@ -461,13 +461,27 @@ class RenderAdapter:
|
||||
from packages.domain.preset_bgm import get_preset_bgm
|
||||
|
||||
preset = get_preset_bgm(preset_id)
|
||||
if preset and preset.audio_url:
|
||||
if preset is None:
|
||||
logger.warning("[plan_id=%s] [BGM] 预设BGM不存在: preset_id=%s", plan_id, preset_id)
|
||||
elif not preset.audio_url:
|
||||
logger.warning(
|
||||
"[plan_id=%s] [BGM] 预设BGM未部署音频文件: preset_id=%s name=%s(audio_url 为空,请运维上传音频后填入 preset_bgm.py)",
|
||||
plan_id,
|
||||
preset_id,
|
||||
preset.name,
|
||||
)
|
||||
else:
|
||||
from video_processing.url_security import (
|
||||
ALLOWED_AUDIO_MIME_TYPES,
|
||||
safe_download_file,
|
||||
)
|
||||
|
||||
logger.info("[plan_id=%s] [BGM] 从预设库下载: preset_id=%s", plan_id, preset_id)
|
||||
logger.info(
|
||||
"[plan_id=%s] [BGM] 从预设库下载: preset_id=%s url=%s",
|
||||
plan_id,
|
||||
preset_id,
|
||||
preset.audio_url[:80],
|
||||
)
|
||||
safe_download_file(
|
||||
preset.audio_url,
|
||||
str(bgm_file),
|
||||
@@ -480,7 +494,14 @@ class RenderAdapter:
|
||||
except Exception as e:
|
||||
logger.warning("[plan_id=%s] [BGM] 预设库下载失败: %s", plan_id, e)
|
||||
|
||||
logger.warning("[plan_id=%s] [BGM] 所有来源都无法获取BGM,跳过", plan_id)
|
||||
logger.warning(
|
||||
"[plan_id=%s] [BGM] 所有来源都无法获取BGM(enabled=%s audio_url=%s asset_id=%s preset_id=%s),跳过",
|
||||
plan_id,
|
||||
bool(bgm_config.get("enabled")),
|
||||
"set" if audio_url else "empty",
|
||||
asset_id[:12] + "…" if len(asset_id) > 12 else asset_id or "empty",
|
||||
preset_id or "empty",
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# 预设 BGM 音频文件放置目录
|
||||
|
||||
将下列 10 首免费可商用 BGM 的 mp3/m4a 文件按 `{preset_id}.mp3` 命名放到本目录:
|
||||
|
||||
| preset_id | 名称 | 风格 | 时长(s) | 标签 |
|
||||
|-------------------|----------|----------|---------|----------------------------|
|
||||
| bgm_upbeat_001 | 阳光清晨 | upbeat | 120 | 轻快 阳光 吉他 vlog |
|
||||
| bgm_upbeat_002 | 活力节拍 | upbeat | 95 | 轻快 电子 活力 运动 |
|
||||
| bgm_upbeat_003 | 夏日漫步 | upbeat | 110 | 轻快 夏日 ukulele 旅行 |
|
||||
| bgm_relax_001 | 静谧时光 | relax | 180 | 治愈 钢琴 安静 冥想 |
|
||||
| bgm_relax_002 | 雨后森林 | relax | 150 | 治愈 自然 放松 环境音 |
|
||||
| bgm_relax_003 | 月光奏鸣曲 | relax | 200 | 治愈 古典 钢琴 优雅(公版) |
|
||||
| bgm_tech_001 | 未来科技 | tech | 85 | 科技 电子 未来感 数码 |
|
||||
| bgm_tech_002 | 数据脉冲 | tech | 100 | 科技 极简 数据 AI |
|
||||
| bgm_commerce_001 | 心动时刻 | commerce | 75 | 电商 时尚 动感 带货 |
|
||||
| bgm_commerce_002 | 品质生活 | commerce | 90 | 电商 高端 品牌 品质 |
|
||||
|
||||
放好后执行(需要在有 OSS 凭证的机器上):
|
||||
```bash
|
||||
export OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
|
||||
export OSS_ACCESS_KEY_ID=xxx
|
||||
export OSS_ACCESS_KEY_SECRET=xxx
|
||||
export OSS_BUCKET_NAME=xiaoxia-autocut
|
||||
python scripts/upload_preset_bgm.py
|
||||
```
|
||||
|
||||
脚本会:
|
||||
1. 上传文件到 OSS `preset/bgm/<preset_id>.mp3`,设置公共读 ACL
|
||||
2. 自动改写 `packages/domain/preset_bgm.py` 把对应 `audio_url=""` 回填成公网 URL
|
||||
3. 提示 `git commit & push`
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user