Files
meijiaka-zy/python-api/app/utils/content_fingerprint.py
T

134 lines
4.0 KiB
Python

"""
内容指纹工具
============
用于 AI 第三方平台(如 Vidu)的审核结果缓存与防重复提交。
核心逻辑:
- 对提交的音频/视频/图片 URL + 任务参数生成 SHA256 指纹。
- 如果相同内容近期因审核失败被缓存,则直接返回错误,不再调用第三方平台。
- 仅规范化 URL(去掉 token 等动态参数),不下载大文件本身。
"""
from __future__ import annotations
import hashlib
from urllib.parse import parse_qs, urlencode, urlparse
# Vidu 内容安全/审核类错误码
# 这些错误在内容不变的情况下重试也没用,应该被缓存。
VIDU_AUDIT_ERROR_CODES = frozenset(
{
"TaskPromptPolicyViolation", # Prompt 触发安审风控
"AuditSubmitIllegal", # 输入未通过安全审核
"CreationPolicyViolation", # 生成物触发风控
"PhotoAuditNotPass", # 图片审核不通过
"AuditFailed", # 审核失败
"ImageCheckBodyJointsFailed", # 输入图人体检测失败
"ImageCheckFaceFailed", # 输入图人脸检测失败
"ImageObjectsUndetected", # 人体或人脸有遮挡
"FaceDetectFailure", # 人脸检测失败
"FaceDetectNotPass", # 人脸检测不通过
"NoFaceDetected", # 未检测到人脸
"MultiFaceDetected", # 检测到多张人脸
}
)
# 常见动态 query 参数,生成指纹时应忽略
_DYNAMIC_QUERY_PARAMS = frozenset(
{
"token",
"e", # 七牛过期时间戳
"t", # 时间戳
"sign",
"x-oss-signature",
"x-oss-expires",
"x-oss-access-key-id",
"response-content-disposition",
"v", # 版本号/缓存戳
}
)
def normalize_url(url: str | None) -> str:
"""规范化 URL,去掉动态参数,确保同一资源不同 token 得到相同指纹。
Args:
url: 原始 URL,可能包含七牛私有 token、时间戳等动态参数
Returns:
规范化后的 URL 字符串,None 或空字符串返回空串
"""
if not url:
return ""
parsed = urlparse(url)
query_params = parse_qs(parsed.query, keep_blank_values=True)
for key in list(query_params.keys()):
if key.lower() in _DYNAMIC_QUERY_PARAMS:
del query_params[key]
query = urlencode(sorted(query_params.items()), doseq=True)
path = parsed.path or "/"
if query:
return f"{parsed.scheme}://{parsed.netloc}{path}?{query}"
return f"{parsed.scheme}://{parsed.netloc}{path}"
def compute_content_fingerprint(
task_type: str,
*,
video_url: str | None = None,
audio_url: str | None = None,
ref_photo_url: str | None = None,
text: str | None = None,
voice_id: str | None = None,
) -> str:
"""计算内容指纹。
指纹字段选择原则:只包含会影响 Vidu 审核结果的输入内容。
不包含 callback_url、speed、volume、payload 等业务/技术参数。
Args:
task_type: 任务类型,如 "lip_sync", "tts", "clone_voice"
video_url: 视频 URL
audio_url: 音频 URL
ref_photo_url: 参考图片 URL
text: 文本/Prompt
voice_id: 音色 ID
Returns:
SHA256 十六进制指纹字符串
"""
parts = [
task_type.strip().lower(),
normalize_url(video_url),
normalize_url(audio_url),
normalize_url(ref_photo_url),
(text or "").strip(),
(voice_id or "").strip().lower(),
]
raw = "|".join(parts)
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
def is_vidu_audit_error(err_code: str | None) -> bool:
"""判断是否为 Vidu 审核类错误码。"""
if not err_code:
return False
return err_code.strip() in VIDU_AUDIT_ERROR_CODES
def extract_vidu_error_code(message: str | None) -> str | None:
"""从 Vidu 错误信息中提取错误码。
Vidu 错误信息格式通常为:"ErrorCode: 中文描述"
"""
if not message:
return None
candidate = message.split(":")[0].strip()
return candidate or None