""" 计费工具模块 — 分辨率映射 + token/费用计算 Token 预估公式(火山官方):(宽 × 高 × 帧率 × 时长) / 1024 单价:元 / 百万 tokens """ from decimal import Decimal, ROUND_HALF_UP # 分辨率 → 像素映射(来自 Seedance 2.0 API 文档) RESOLUTION_MAP = { # 720p ('720p', '16:9'): (1280, 720), ('720p', '9:16'): (720, 1280), ('720p', '4:3'): (1112, 834), ('720p', '1:1'): (960, 960), ('720p', '3:4'): (834, 1112), ('720p', '21:9'): (1470, 630), # 480p ('480p', '16:9'): (864, 496), ('480p', '9:16'): (496, 864), ('480p', '4:3'): (752, 560), ('480p', '1:1'): (640, 640), ('480p', '3:4'): (560, 752), ('480p', '21:9'): (992, 432), # 1080p (来自火山 API 文档,Seedance 2.0 & 2.0 fast 列) ('1080p', '16:9'): (1920, 1080), ('1080p', '9:16'): (1080, 1920), ('1080p', '4:3'): (1664, 1248), ('1080p', '1:1'): (1440, 1440), ('1080p', '3:4'): (1248, 1664), ('1080p', '21:9'): (2206, 946), } # 默认帧率 DEFAULT_FPS = 24 def get_resolution(aspect_ratio: str, tier: str) -> tuple: """根据宽高比和分辨率档位返回 (width, height) 像素值。 tier 必填,不设默认值 — 避免调用者遗漏时静默降级为 720p(违反计费准确性原则)。 若 (tier, aspect_ratio) 组合不在 RESOLUTION_MAP(如 adaptive),raise KeyError, 让上游感知并 fail loud。上游(serializer/前端)负责保证合法组合。 """ key = (tier, aspect_ratio) if key not in RESOLUTION_MAP: raise KeyError( f'不支持的分辨率组合: tier={tier!r}, aspect_ratio={aspect_ratio!r}. ' f'仅支持 480p/720p/1080p × 16:9/9:16/4:3/1:1/3:4/21:9' ) return RESOLUTION_MAP[key] def estimate_tokens( width: int, height: int, duration: int, fps: int = DEFAULT_FPS, input_video_duration: float = 0, ) -> int: """预估视频生成消耗的 tokens。 火山官方公式:`(输入视频时长 + 输出视频时长) × 宽 × 高 × 帧率 / 1024` ⚠️ 这是预估值,仅用于前端展示和额度冻结。 真实费用以火山 API 返回的 usage.total_tokens 为准(`_settle_payment` 中按实际值结算)。 最低 token 用量限制是火山计费端的逻辑,我方不在预估端维护该表(避免与官方脱钩)。 Args: width: 输出视频宽度(像素) height: 输出视频高度(像素) duration: 输出视频时长(秒) fps: 帧率,默认 24 input_video_duration: 输入参考视频的总时长(秒),默认 0 Returns: token 估算值(整数) """ total_duration = duration + (input_video_duration or 0) return round(width * height * fps * total_duration / 1024) def calculate_cost(tokens: int, base_price, markup_percentage) -> Decimal: """计算用户费用(加价后)。 Args: tokens: 消耗的 tokens 数 base_price: 成本价(元/百万tokens) markup_percentage: 加价百分比,如 20 表示 20% Returns: Decimal: 加价后费用,保留 2 位小数 """ base_price = Decimal(str(base_price)) markup = Decimal(str(markup_percentage)) team_price = base_price * (1 + markup / 100) cost = Decimal(str(tokens)) * team_price / Decimal('1000000') return cost.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) def calculate_base_cost(tokens: int, base_price) -> Decimal: """计算平台成本(不加价)。 Args: tokens: 消耗的 tokens 数 base_price: 成本价(元/百万tokens) Returns: Decimal: 成本费用,保留 2 位小数 """ base_price = Decimal(str(base_price)) cost = Decimal(str(tokens)) * base_price / Decimal('1000000') return cost.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)