06ec0ee202
后端: - 新增 BGM 数据库模型、Schema、CRUD、API 路由 - BgmMusic 增加 url 字段存储七牛云地址 - Alembic 迁移: 创建 BGM 表 + 添加 url 字段 - import_bgm.py 导入时自动上传七牛云 (meijiaka-zy/bgm/...) 前端: - VideoCompose BGM 选择改为卡片弹窗 (系统BGM + 本地上传) - 去掉 BGM 硬编码本地路径, 直接使用云端 URL - CoverDesign 视觉重构: 绿色边框卡片、角标、hover 遮罩 - CoverDesign 去掉预选背景, 默认空白需手动选择 - 所有步骤按钮规范统一: 左=重新生成(主色), 右=导出/预览(次色) - 预览按钮状态统一: 文字变为'视频预览中...', 保持 btn-secondary - 去掉所有步骤按钮的 svg/emoji 图标 Rust: - mix_bgm_to_video 支持临时文件保护 (输入输出同路径时自动中转) - FFmpeg BGM 混合使用 aloop 循环 + amix 滤镜
137 lines
4.4 KiB
Python
137 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
导入 Mixkit BGM 音频文件到数据库并上传七牛云
|
||
扫描 mixkit_bgm/ 目录下的分类文件夹,读取音频元数据并插入 mjk_bgm_musics 表
|
||
同时上传音频到七牛云视频/音频 bucket,保存 URL。
|
||
"""
|
||
|
||
import asyncio
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# 将项目根目录加入 Python 路径
|
||
project_root = Path(__file__).parent.parent
|
||
sys.path.insert(0, str(project_root))
|
||
|
||
# 加载环境变量
|
||
from dotenv import load_dotenv
|
||
|
||
load_dotenv()
|
||
|
||
from mutagen.mp3 import MP3
|
||
from sqlalchemy import select
|
||
from app.db.session import AsyncSessionLocal
|
||
from app.models.bgm_music import BgmMusic
|
||
from app.services.qiniu_service import get_qiniu_service
|
||
|
||
BGM_BASE_DIR = Path("/Users/0fun/work/meijiaka-zy/mixkit_bgm")
|
||
|
||
|
||
def parse_title(filename: str) -> str:
|
||
"""从文件名解析标题,如 '105_See_Line_Funk.mp3' -> 'See Line Funk'"""
|
||
# 去掉扩展名
|
||
name = filename.rsplit(".", 1)[0]
|
||
# 去掉开头的编号前缀(如 105_)
|
||
parts = name.split("_", 1)
|
||
if len(parts) > 1 and parts[0].isdigit():
|
||
title_part = parts[1]
|
||
else:
|
||
title_part = name
|
||
# 将下划线替换为空格
|
||
return title_part.replace("_", " ")
|
||
|
||
|
||
def get_duration(filepath: Path) -> float | None:
|
||
"""获取音频文件时长(秒)"""
|
||
try:
|
||
audio = MP3(str(filepath))
|
||
return audio.info.length
|
||
except Exception as e:
|
||
print(f" 无法读取时长: {filepath.name} ({e})")
|
||
return None
|
||
|
||
|
||
async def import_bgm():
|
||
"""扫描目录并导入数据库,同时上传七牛云"""
|
||
if not BGM_BASE_DIR.exists():
|
||
print(f"错误: BGM 目录不存在: {BGM_BASE_DIR}")
|
||
return
|
||
|
||
categories = [d.name for d in BGM_BASE_DIR.iterdir() if d.is_dir()]
|
||
print(f"发现分类: {categories}")
|
||
|
||
# 初始化七牛云服务
|
||
try:
|
||
qiniu = get_qiniu_service()
|
||
print("七牛云服务初始化成功")
|
||
except ValueError as e:
|
||
print(f"七牛云配置错误: {e}")
|
||
return
|
||
|
||
async with AsyncSessionLocal() as session:
|
||
# 清空现有数据(可选)
|
||
result = await session.execute(select(BgmMusic))
|
||
existing = result.scalars().all()
|
||
if existing:
|
||
print(f"数据库中已有 {len(existing)} 条 BGM 记录,将删除后重新导入")
|
||
for row in existing:
|
||
await session.delete(row)
|
||
await session.commit()
|
||
|
||
total = 0
|
||
upload_ok = 0
|
||
upload_fail = 0
|
||
for category in sorted(categories):
|
||
cat_dir = BGM_BASE_DIR / category
|
||
files = sorted([f for f in cat_dir.iterdir() if f.suffix.lower() in (".mp3", ".wav", ".m4a")])
|
||
print(f"\n分类 [{category}]: {len(files)} 首")
|
||
|
||
for idx, filepath in enumerate(files):
|
||
title = parse_title(filepath.name)
|
||
relative_path = f"{category}/{filepath.name}"
|
||
duration = get_duration(filepath)
|
||
|
||
# 上传七牛云
|
||
qiniu_key = f"meijiaka-zy/bgm/{relative_path}"
|
||
url = None
|
||
try:
|
||
upload_result = qiniu.upload_file(
|
||
local_path=str(filepath),
|
||
key=qiniu_key,
|
||
file_type="audio",
|
||
check_duplicate=True,
|
||
)
|
||
url = upload_result.get("url")
|
||
if upload_result.get("isDuplicate"):
|
||
print(f" + {title} (复用已有文件)")
|
||
else:
|
||
print(f" + {title} (上传成功)")
|
||
upload_ok += 1
|
||
except Exception as e:
|
||
print(f" ! {title} (上传失败: {e})")
|
||
upload_fail += 1
|
||
# 上传失败也继续入库,只是 url 为空
|
||
|
||
bgm = BgmMusic(
|
||
title=title,
|
||
artist=None,
|
||
category=category,
|
||
file_path=relative_path,
|
||
url=url,
|
||
duration=duration,
|
||
status="active",
|
||
sort_order=idx,
|
||
)
|
||
session.add(bgm)
|
||
total += 1
|
||
|
||
await session.commit()
|
||
print(f"\n导入完成,共 {total} 首")
|
||
print(f" 上传成功: {upload_ok} 首")
|
||
print(f" 上传失败: {upload_fail} 首")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(import_bgm())
|