feat(sync): 长语音音频本地化(voice_clip media_hash+download+local-first playback)
Mirror download_call_recordings pattern for voice_clips:
- VoiceClip.media_hash column + Alembic migration (c4d5e6f7a8b9)
- VoiceClipOut.media_hash + frontend VoiceClip.media_hash type
- download_voice_clip_audio in app/scraper/media.py (resilient, sha256-dedup,
same download_url/upsert_media helpers as call recordings)
- sync_voice_clip_audio orchestrator wrapper (commits own SyncRun, mirrors
sync_call_recordings semantics)
- scheduler: _run_voice_clip_audio + ENTITY_FUNCS['voice_clip_audio'] +
appended to daily _run_all job (after voice_clips)
- VoiceClipModal playClip local-first: /api/media/{media_hash} with OSS
oss_file_name fallback (mirrors CallList.playRecord)
Tested: 376 pytest pass; vue-tsc clean; migration applied; 1523/1533 clips
localized (10 pending are genuine upstream HTTP 404s — resilient skip).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -516,6 +516,12 @@ class VoiceClip(Base):
|
||||
duration_format: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||||
channel: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||
|
||||
# sha256 media_hash of the localized audio blob (FK -> Media.media_hash);
|
||||
# NULL until sync_voice_clip_audio downloads it. Populated by the
|
||||
# voice_clip_audio sync pass; playback prefers /api/media/{this} over the
|
||||
# OSS oss_file_name URL.
|
||||
media_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
|
||||
# LOCAL-only manual match: references customer.friend_id when an operator
|
||||
# has associated this clip with a specific customer (好友). NULL = unmatched.
|
||||
# The mirror never pushes this back to 云客 — it's our own bookkeeping for
|
||||
|
||||
Reference in New Issue
Block a user