fix(worker): 素材下载改用storage_key字段,修复file_url为URL导致下载无效文件
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Failing after 0s
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 13s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 40s
AI Code Review / AI Code Review (pull_request) Failing after 1m23s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m47s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 3m6s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 3m27s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1m5s
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Failing after 0s
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 13s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 40s
AI Code Review / AI Code Review (pull_request) Failing after 1m23s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m47s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 3m6s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 3m27s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1m5s
- AssetModel新增storage_key字段(OSS存储键) - render_adapter _download_assets改用a.storage_key - render_adapter BGM下载改用storage_key - edit_plan_generation legacy路径同步修复 - generation.py BGM下载同步修复 - asset_repository映射同步更新storage_key字段 - 单元测试同步更新
This commit is contained in:
@@ -366,12 +366,12 @@ class RenderAdapter:
|
||||
failed_clip_ids: list[str] = []
|
||||
seen_asset_ids: set[str] = set()
|
||||
|
||||
# 批量查询素材的 file_url(OSS 存储路径)
|
||||
# 批量查询素材的 storage_key(OSS 存储路径)
|
||||
clip_asset_ids = [c.asset_id for c in clips if c.asset_id]
|
||||
asset_storage_map: dict[str, str] = {}
|
||||
if clip_asset_ids:
|
||||
assets = self._db.query(AssetModel).filter(AssetModel.id.in_(clip_asset_ids)).all()
|
||||
asset_storage_map = {a.id: a.file_url for a in assets if a.file_url}
|
||||
asset_storage_map = {a.id: a.storage_key for a in assets if a.storage_key}
|
||||
|
||||
for clip in clips:
|
||||
asset_id = clip.asset_id
|
||||
@@ -468,8 +468,8 @@ class RenderAdapter:
|
||||
from packages.adapters.sqlalchemy_impl.models import AssetModel
|
||||
|
||||
model = self._db.query(AssetModel).filter(AssetModel.id == asset_id).first()
|
||||
if model and model.file_url:
|
||||
storage_key = model.file_url
|
||||
if model and model.storage_key:
|
||||
storage_key = model.storage_key
|
||||
logger.info("[plan_id=%s] [BGM] 从素材库下载: asset_id=%s", plan_id, asset_id)
|
||||
ok = download_asset(storage_key, bgm_file)
|
||||
if ok and bgm_file.exists() and bgm_file.stat().st_size > 0:
|
||||
|
||||
@@ -472,14 +472,14 @@ def render_edit_plan(self, plan_id: str) -> dict:
|
||||
rendered_clip_ids: list[str] = []
|
||||
failed_clip_ids: list[str] = []
|
||||
|
||||
# 预先批量查询所有素材的 storage_key(file_url)
|
||||
# 预先批量查询所有素材的 storage_key
|
||||
from packages.adapters.sqlalchemy_impl.models import AssetModel
|
||||
|
||||
clip_asset_ids = [c.asset_id for c in clips if c.asset_id]
|
||||
asset_storage_map: dict[str, str] = {}
|
||||
if clip_asset_ids:
|
||||
assets = db.query(AssetModel).filter(AssetModel.id.in_(clip_asset_ids)).all()
|
||||
asset_storage_map = {a.id: a.file_url for a in assets if a.file_url}
|
||||
asset_storage_map = {a.id: a.storage_key for a in assets if a.storage_key}
|
||||
|
||||
for clip in clips:
|
||||
if not clip.asset_id:
|
||||
|
||||
@@ -537,8 +537,8 @@ def _prepare_bgm_track(
|
||||
session = SessionLocal()
|
||||
try:
|
||||
model = session.query(AssetModel).filter(AssetModel.id == asset_id).first()
|
||||
if model and model.file_url:
|
||||
storage_key = model.file_url
|
||||
if model and model.storage_key:
|
||||
storage_key = model.storage_key
|
||||
logger.info("[task_id=%s] [BGM] 从素材库下载: asset_id=%s", task_id, asset_id)
|
||||
ok = download_asset(storage_key, bgm_file)
|
||||
if ok and bgm_file.exists() and bgm_file.stat().st_size > 0:
|
||||
|
||||
@@ -72,6 +72,7 @@ class SQLAlchemyAssetRepository:
|
||||
file_type=(asset.mime_type.split("/")[0] if "/" in asset.mime_type else asset.mime_type),
|
||||
file_size=asset.file_size,
|
||||
file_url=asset.storage_key,
|
||||
storage_key=asset.storage_key,
|
||||
thumbnail_url=asset.thumbnail_url,
|
||||
duration=asset.duration,
|
||||
width=asset.width,
|
||||
@@ -100,6 +101,7 @@ class SQLAlchemyAssetRepository:
|
||||
model.name = asset.name
|
||||
model.file_size = asset.file_size
|
||||
model.file_url = asset.storage_key
|
||||
model.storage_key = asset.storage_key
|
||||
model.thumbnail_url = asset.thumbnail_url
|
||||
model.duration = asset.duration
|
||||
model.width = asset.width
|
||||
@@ -299,7 +301,7 @@ class SQLAlchemyAssetRepository:
|
||||
project_id=model.project_id,
|
||||
library_id=model.asset_library_id,
|
||||
name=model.name,
|
||||
storage_key=model.file_url,
|
||||
storage_key=model.storage_key or model.file_url,
|
||||
mime_type=mime_type,
|
||||
file_size=int(model.file_size or 0),
|
||||
thumbnail_url=model.thumbnail_url,
|
||||
|
||||
@@ -75,6 +75,8 @@ class AssetModel(Base):
|
||||
file_size = Column(Integer, nullable=False)
|
||||
# file_url: 完整可访问的 URL,用于客户端直接访问文件
|
||||
file_url = Column(String(1000), nullable=False)
|
||||
# storage_key: OSS 存储键,用于内部下载上传
|
||||
storage_key = Column(String(500), nullable=False, default="", server_default="")
|
||||
thumbnail_url = Column(String(1000), nullable=True)
|
||||
duration = Column(Float, nullable=True)
|
||||
width = Column(Float, nullable=True)
|
||||
|
||||
@@ -77,7 +77,7 @@ def _make_adapter(
|
||||
Args:
|
||||
plan: 模拟的剪辑计划
|
||||
clips: 模拟的片段列表
|
||||
asset_url_map: asset_id → file_url 映射,用于 mock assets 表查询
|
||||
asset_url_map: asset_id → storage_key 映射,用于 mock assets 表查询
|
||||
|
||||
Returns:
|
||||
(adapter, mock_plan_repo, mock_clip_repo)
|
||||
@@ -91,7 +91,7 @@ def _make_adapter(
|
||||
for aid, url in asset_url_map.items():
|
||||
m = MagicMock()
|
||||
m.id = aid
|
||||
m.file_url = url
|
||||
m.storage_key = url
|
||||
mock_assets.append(m)
|
||||
mock_query.all.return_value = mock_assets
|
||||
mock_query.filter.return_value = mock_query
|
||||
@@ -521,17 +521,17 @@ class TestRenderPlan:
|
||||
class TestDownloadAssets:
|
||||
@staticmethod
|
||||
def _make_mock_db(asset_url_map: dict[str, str]):
|
||||
"""构造 mock db,根据 asset_id 返回对应的 AssetModel.file_url。"""
|
||||
"""构造 mock db,根据 asset_id 返回对应的 AssetModel.storage_key。"""
|
||||
mock_db = MagicMock()
|
||||
mock_query = MagicMock()
|
||||
|
||||
def _fake_filter(query):
|
||||
# 模拟 .filter(AssetModel.id.in_([...])).all()
|
||||
mock_assets = []
|
||||
for asset_id, file_url in asset_url_map.items():
|
||||
for asset_id, storage_key in asset_url_map.items():
|
||||
mock_asset = MagicMock()
|
||||
mock_asset.id = asset_id
|
||||
mock_asset.file_url = file_url
|
||||
mock_asset.storage_key = storage_key
|
||||
mock_assets.append(mock_asset)
|
||||
mock_query.all.return_value = mock_assets
|
||||
return mock_query
|
||||
@@ -565,7 +565,7 @@ class TestDownloadAssets:
|
||||
assert len(rendered_ids) == 2
|
||||
assert len(failed_ids) == 0
|
||||
assert mock_download.call_count == 2
|
||||
# 验证传给 download_asset 的是 file_url 而非 asset_id
|
||||
# 验证传给 download_asset 的是 storage_key 而非 asset_id
|
||||
call_keys = [call[0][0] for call in mock_download.call_args_list]
|
||||
assert "https://bucket.oss.com/videos/key1.mp4" in call_keys
|
||||
assert "https://bucket.oss.com/videos/key2.mp4" in call_keys
|
||||
@@ -670,14 +670,14 @@ class TestDownloadAssets:
|
||||
assert "c2" in rendered_ids
|
||||
|
||||
@patch("video_processing.render_adapter.download_asset")
|
||||
def test_asset_without_file_url_skipped(self, mock_download, tmp_path):
|
||||
"""素材在assets表中无file_url时跳过下载,标记为失败。"""
|
||||
# 构造返回 asset 但 file_url 为空
|
||||
def test_asset_without_storage_key_skipped(self, mock_download, tmp_path):
|
||||
"""素材在assets表中无storage_key时跳过下载,标记为失败。"""
|
||||
# 构造返回 asset 但 storage_key 为空
|
||||
mock_db = MagicMock()
|
||||
mock_query = MagicMock()
|
||||
mock_asset = MagicMock()
|
||||
mock_asset.id = "asset_no_url"
|
||||
mock_asset.file_url = ""
|
||||
mock_asset.storage_key = ""
|
||||
mock_query.all.return_value = [mock_asset]
|
||||
mock_query.filter.return_value = mock_query
|
||||
mock_db.query.return_value = mock_query
|
||||
|
||||
Reference in New Issue
Block a user