f1621ace9f
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m17s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m26s
CI/CD Pipeline / Integration Tests (push) Successful in 3m48s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m13s
CI/CD Pipeline / Build Staging API Image (push) Successful in 4m44s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
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 / Validate - Python (mypy + alembic) (push) Successful in 5m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / Validate - Style (push) Successful in 7m51s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m54s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m44s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m58s
CI/CD Pipeline / Unit Tests (push) Successful in 10m42s
CI/CD Pipeline / Validate - Security (push) Successful in 12m25s
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 / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
"""素材原子片段仓储 SQLAlchemy 实现。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from packages.adapters.sqlalchemy_impl.models import AssetAtomClipModel
|
|
from packages.domain.asset_atom_clip import AssetAtomClip
|
|
|
|
|
|
class SQLAlchemyAssetAtomClipRepository:
|
|
def __init__(self, session: Session):
|
|
self.session = session
|
|
|
|
def create(self, clip: AssetAtomClip) -> AssetAtomClip:
|
|
model = self._to_model(clip)
|
|
self.session.add(model)
|
|
self.session.flush()
|
|
self.session.commit()
|
|
return clip
|
|
|
|
def batch_create(self, clips: list[AssetAtomClip]) -> list[AssetAtomClip]:
|
|
if not clips:
|
|
return []
|
|
models = [self._to_model(c) for c in clips]
|
|
self.session.add_all(models)
|
|
self.session.flush()
|
|
self.session.commit()
|
|
return clips
|
|
|
|
def find_by_asset(self, asset_id: str) -> list[AssetAtomClip]:
|
|
models = (
|
|
self.session.query(AssetAtomClipModel)
|
|
.filter(AssetAtomClipModel.asset_id == asset_id)
|
|
.order_by(AssetAtomClipModel.clip_index.asc())
|
|
.all()
|
|
)
|
|
return [self._to_domain(m) for m in models]
|
|
|
|
def find_by_id(self, clip_id: str) -> AssetAtomClip | None:
|
|
model = self.session.query(AssetAtomClipModel).filter(AssetAtomClipModel.id == clip_id).first()
|
|
if model is None:
|
|
return None
|
|
return self._to_domain(model)
|
|
|
|
def find_by_ids(self, clip_ids: list[str]) -> list[AssetAtomClip]:
|
|
if not clip_ids:
|
|
return []
|
|
models = self.session.query(AssetAtomClipModel).filter(AssetAtomClipModel.id.in_(clip_ids)).all()
|
|
return [self._to_domain(m) for m in models]
|
|
|
|
def delete_by_asset(self, asset_id: str) -> int:
|
|
count = (
|
|
self.session.query(AssetAtomClipModel)
|
|
.filter(AssetAtomClipModel.asset_id == asset_id)
|
|
.delete(synchronize_session=False)
|
|
)
|
|
self.session.commit()
|
|
return count
|
|
|
|
def count_by_asset(self, asset_id: str) -> int:
|
|
return self.session.query(AssetAtomClipModel).filter(AssetAtomClipModel.asset_id == asset_id).count()
|
|
|
|
def find_candidates_for_selection(
|
|
self,
|
|
asset_ids: list[str],
|
|
*,
|
|
min_duration: float | None = None,
|
|
max_duration: float | None = None,
|
|
limit: int = 100,
|
|
) -> list[AssetAtomClip]:
|
|
"""按筛选条件查找候选原子片段,按时长排序。用于选片逻辑。"""
|
|
query = self.session.query(AssetAtomClipModel).filter(AssetAtomClipModel.asset_id.in_(asset_ids))
|
|
if min_duration is not None:
|
|
query = query.filter(AssetAtomClipModel.duration >= min_duration)
|
|
if max_duration is not None:
|
|
query = query.filter(AssetAtomClipModel.duration <= max_duration)
|
|
query = query.order_by(AssetAtomClipModel.clip_index.asc())
|
|
if limit > 0:
|
|
query = query.limit(limit)
|
|
models = query.all()
|
|
return [self._to_domain(m) for m in models]
|
|
|
|
def _to_model(self, clip: AssetAtomClip) -> AssetAtomClipModel:
|
|
return AssetAtomClipModel(
|
|
id=clip.id,
|
|
asset_id=clip.asset_id,
|
|
start_time=clip.start_time,
|
|
end_time=clip.end_time,
|
|
duration=clip.duration,
|
|
clip_index=clip.clip_index,
|
|
tags=clip.tags,
|
|
scene_change_at=clip.scene_change_at,
|
|
is_fallback=clip.is_fallback,
|
|
created_at=clip.created_at or datetime.now(UTC),
|
|
)
|
|
|
|
def _to_domain(self, model: AssetAtomClipModel) -> AssetAtomClip:
|
|
return AssetAtomClip(
|
|
id=model.id,
|
|
asset_id=model.asset_id,
|
|
start_time=model.start_time,
|
|
end_time=model.end_time,
|
|
duration=model.duration,
|
|
clip_index=model.clip_index,
|
|
tags=model.tags or [],
|
|
scene_change_at=model.scene_change_at,
|
|
is_fallback=model.is_fallback,
|
|
created_at=model.created_at,
|
|
)
|