增加即梦图片5.0和视频2.5的能力
This commit is contained in:
1 parent
542246e4ce
commit
88a4268d40
55 files changed
+1412
-200
No files matched your search
@@ -1,9 +1,10 @@
|
||||
import { VIDEO_DURATION_MAX } from "@/lib/video-settings";
|
||||
import { SEEDANCE_25_MODEL, VIDEO_DURATION_MAX, isSeedance25Model } from "@/lib/video-settings";
|
||||
|
||||
const SEEDANCE_FPS = 24;
|
||||
const SEEDANCE_TOKEN_SCALE = 1_000_000;
|
||||
|
||||
export type SeedanceBillingEstimateInput = {
|
||||
model?: unknown;
|
||||
resolution?: unknown;
|
||||
aspectRatio?: unknown;
|
||||
outputDurationSeconds: number;
|
||||
@@ -13,6 +14,7 @@ export type SeedanceBillingEstimateInput = {
|
||||
};
|
||||
|
||||
export type SeedanceActualBillingInput = {
|
||||
model?: unknown;
|
||||
resolution?: unknown;
|
||||
inputVideo?: boolean;
|
||||
completionTokens: number;
|
||||
@@ -20,11 +22,17 @@ export type SeedanceActualBillingInput = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 火山方舟文档的 Seedance 2.0 视频 token 单价,单位为“分/百万 token”。
|
||||
* 480p/720p、1080p 和 4K 的单价不同;含输入视频时使用对应的输入视频档。
|
||||
* 火山方舟文档的 Seedance 视频 token 单价,单位为“分/百万 token”。
|
||||
* 每个模型、分辨率及是否包含输入视频都有独立价格。
|
||||
*/
|
||||
export function seedanceTokenPriceFenPerMillion(resolution: unknown, inputVideo = false): number {
|
||||
export function seedanceTokenPriceFenPerMillion(model: unknown, resolution: unknown, inputVideo = false): number {
|
||||
const normalized = normalizeSeedanceResolution(resolution);
|
||||
if (String(model || "").trim() === SEEDANCE_25_MODEL) {
|
||||
const prices = normalized === "1080p"
|
||||
? { withoutVideo: 7_700, withVideo: 4_600 }
|
||||
: { withoutVideo: 7_000, withVideo: 4_200 };
|
||||
return inputVideo ? prices.withVideo : prices.withoutVideo;
|
||||
}
|
||||
const prices: Record<string, { withoutVideo: number; withVideo: number }> = {
|
||||
"480p": { withoutVideo: 4_600, withVideo: 2_800 },
|
||||
"720p": { withoutVideo: 4_600, withVideo: 2_800 },
|
||||
@@ -42,7 +50,7 @@ export function calculateSeedanceActualAmountFen(input: SeedanceActualBillingInp
|
||||
if (!Number.isFinite(input.markupMultiplier) || input.markupMultiplier < 1) {
|
||||
throw new Error("Seedance 计费倍率不能低于 1.00。");
|
||||
}
|
||||
const tokenPriceFenPerMillion = seedanceTokenPriceFenPerMillion(input.resolution, input.inputVideo);
|
||||
const tokenPriceFenPerMillion = seedanceTokenPriceFenPerMillion(input.model, input.resolution, input.inputVideo);
|
||||
return Math.max(1, Math.ceil(
|
||||
input.completionTokens * tokenPriceFenPerMillion * input.markupMultiplier / SEEDANCE_TOKEN_SCALE
|
||||
));
|
||||
@@ -54,7 +62,7 @@ export function estimateSeedanceAmountFen(input: SeedanceBillingEstimateInput):
|
||||
}
|
||||
const inputVideo = Boolean(input.inputVideo);
|
||||
const inputDuration = inputVideo
|
||||
? normalizeInputVideoDuration(input.inputVideoDurationSeconds)
|
||||
? normalizeInputVideoDuration(input.inputVideoDurationSeconds, input.model)
|
||||
: 0;
|
||||
const completionTokens = estimateSeedanceCompletionTokens({
|
||||
resolution: input.resolution,
|
||||
@@ -63,7 +71,7 @@ export function estimateSeedanceAmountFen(input: SeedanceBillingEstimateInput):
|
||||
inputVideoDurationSeconds: inputDuration
|
||||
});
|
||||
return Math.max(1, Math.ceil(
|
||||
completionTokens * seedanceTokenPriceFenPerMillion(input.resolution, inputVideo) * input.markupMultiplier / SEEDANCE_TOKEN_SCALE
|
||||
completionTokens * seedanceTokenPriceFenPerMillion(input.model, input.resolution, inputVideo) * input.markupMultiplier / SEEDANCE_TOKEN_SCALE
|
||||
));
|
||||
}
|
||||
|
||||
@@ -84,10 +92,11 @@ export function estimateSeedanceCompletionTokens(input: {
|
||||
/**
|
||||
* 没有媒体元数据时按官方可接收的最大输入视频时长冻结,避免先扣少、成功后补扣造成余额不足。
|
||||
*/
|
||||
export function normalizeInputVideoDuration(value: unknown): number {
|
||||
export function normalizeInputVideoDuration(value: unknown, model?: unknown): number {
|
||||
const duration = Number(value);
|
||||
if (!Number.isFinite(duration) || duration <= 0) return VIDEO_DURATION_MAX;
|
||||
return Math.min(VIDEO_DURATION_MAX, duration);
|
||||
const maximum = isSeedance25Model(String(model || "")) ? 30 : VIDEO_DURATION_MAX;
|
||||
if (!Number.isFinite(duration) || duration <= 0) return maximum;
|
||||
return Math.min(maximum, isSeedance25Model(String(model || "")) ? Math.max(4, duration) : duration);
|
||||
}
|
||||
|
||||
export function normalizeSeedanceResolution(value: unknown): string {
|
||||
@@ -116,7 +125,7 @@ export function requestHasInputVideo(requestPayload: Record<string, unknown>): b
|
||||
return requestMaterials(requestPayload).some((material) => String(material.type || "").toLowerCase() === "video");
|
||||
}
|
||||
|
||||
export function requestInputVideoDurationSeconds(requestPayload: Record<string, unknown>): number | undefined {
|
||||
export function requestInputVideoDurationSeconds(requestPayload: Record<string, unknown>, model?: unknown): number | undefined {
|
||||
const videoMaterials = requestMaterials(requestPayload)
|
||||
.filter((material) => String(material.type || "").toLowerCase() === "video");
|
||||
if (!videoMaterials.length) return undefined;
|
||||
@@ -129,7 +138,7 @@ export function requestInputVideoDurationSeconds(requestPayload: Record<string,
|
||||
))
|
||||
.filter((value): value is number => value !== undefined && value > 0);
|
||||
if (!knownDurations.length) return undefined;
|
||||
return Math.min(VIDEO_DURATION_MAX, knownDurations.reduce((sum, value) => sum + value, 0));
|
||||
return normalizeInputVideoDuration(knownDurations.reduce((sum, value) => sum + value, 0), model);
|
||||
}
|
||||
|
||||
function requestMaterials(requestPayload: Record<string, unknown>): Array<Record<string, unknown>> {
|
||||
|
||||
Reference in new issue
Block a user