1
This commit is contained in:
@@ -37,7 +37,8 @@ const SUNO_TAGS_MAX_CHARS: usize = 160;
|
||||
const VIDU_PROMPT_MAX_CHARS: usize = 1_500;
|
||||
const DEFAULT_SOUND_EFFECT_DURATION_SECONDS: u8 = 5;
|
||||
const MAX_GENERATED_AUDIO_BYTES: usize = 40 * 1024 * 1024;
|
||||
const CREATION_AUDIO_POINTS_COST: u64 = 10;
|
||||
const CREATION_BACKGROUND_MUSIC_POINTS_COST: u64 = 5;
|
||||
const CREATION_SOUND_EFFECT_POINTS_COST: u64 = 10;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VectorEngineAudioSettings {
|
||||
@@ -260,13 +261,8 @@ pub(crate) async fn generate_sound_effect_asset_for_creation(
|
||||
target: GeneratedCreationAudioTarget,
|
||||
) -> Result<creation_audio::CreationAudioAsset, AppError> {
|
||||
let normalized_prompt = normalize_limited_text(&prompt, "prompt", VIDU_PROMPT_MAX_CHARS)?;
|
||||
let task = create_sound_effect_task_response(
|
||||
state,
|
||||
normalized_prompt.clone(),
|
||||
duration,
|
||||
seed,
|
||||
)
|
||||
.await?;
|
||||
let task =
|
||||
create_sound_effect_task_response(state, normalized_prompt.clone(), duration, seed).await?;
|
||||
let target = AudioAssetBindingTarget {
|
||||
storage_scope: target.entity_kind.clone(),
|
||||
entity_kind: target.entity_kind,
|
||||
@@ -284,9 +280,9 @@ pub(crate) async fn generate_sound_effect_asset_for_creation(
|
||||
target,
|
||||
)
|
||||
.await?;
|
||||
let audio_src = generated.audio_src.ok_or_else(|| {
|
||||
vector_engine_bad_gateway("音效生成完成但缺少播放地址")
|
||||
})?;
|
||||
let audio_src = generated
|
||||
.audio_src
|
||||
.ok_or_else(|| vector_engine_bad_gateway("音效生成完成但缺少播放地址"))?;
|
||||
|
||||
Ok(creation_audio::CreationAudioAsset {
|
||||
task_id: generated.task_id,
|
||||
@@ -309,11 +305,8 @@ pub(crate) async fn generate_background_music_asset_for_creation(
|
||||
model: Option<String>,
|
||||
target: GeneratedCreationAudioTarget,
|
||||
) -> Result<creation_audio::CreationAudioAsset, AppError> {
|
||||
let normalized_prompt = normalize_limited_text_allow_empty(
|
||||
&prompt,
|
||||
"prompt",
|
||||
SUNO_PROMPT_MAX_CHARS,
|
||||
)?;
|
||||
let normalized_prompt =
|
||||
normalize_limited_text_allow_empty(&prompt, "prompt", SUNO_PROMPT_MAX_CHARS)?;
|
||||
let normalized_title = normalize_limited_text(&title, "title", SUNO_TITLE_MAX_CHARS)?;
|
||||
let task = create_background_music_task_response(
|
||||
state,
|
||||
@@ -340,9 +333,9 @@ pub(crate) async fn generate_background_music_asset_for_creation(
|
||||
target,
|
||||
)
|
||||
.await?;
|
||||
let audio_src = generated.audio_src.ok_or_else(|| {
|
||||
vector_engine_bad_gateway("背景音乐生成完成但缺少播放地址")
|
||||
})?;
|
||||
let audio_src = generated
|
||||
.audio_src
|
||||
.ok_or_else(|| vector_engine_bad_gateway("背景音乐生成完成但缺少播放地址"))?;
|
||||
|
||||
Ok(creation_audio::CreationAudioAsset {
|
||||
task_id: generated.task_id,
|
||||
@@ -623,12 +616,13 @@ async fn publish_generated_audio_asset(
|
||||
.ok_or_else(|| vector_engine_bad_gateway("音频生成尚未返回可下载地址"))?;
|
||||
let billing_asset_kind = target.asset_kind.clone();
|
||||
let billing_asset_id = build_audio_billing_asset_id(&task_id, slot, &target);
|
||||
let points_cost = resolve_creation_audio_points_cost(slot, &target);
|
||||
let persisted = execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
owner_user_id,
|
||||
billing_asset_kind.as_str(),
|
||||
billing_asset_id.as_str(),
|
||||
CREATION_AUDIO_POINTS_COST,
|
||||
points_cost,
|
||||
async {
|
||||
let audio = download_generated_audio(&http_client, &audio_url, slot.provider()).await?;
|
||||
persist_generated_audio_asset(
|
||||
@@ -673,7 +667,12 @@ async fn wait_for_generated_audio_asset(
|
||||
target.clone(),
|
||||
)
|
||||
.await?;
|
||||
if response.audio_src.as_deref().map(str::trim).is_some_and(|value| !value.is_empty()) {
|
||||
if response
|
||||
.audio_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
latest_status = response.status;
|
||||
@@ -704,6 +703,16 @@ fn build_audio_billing_asset_id(
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_creation_audio_points_cost(
|
||||
slot: AudioAssetSlot,
|
||||
_target: &AudioAssetBindingTarget,
|
||||
) -> u64 {
|
||||
match slot {
|
||||
AudioAssetSlot::BackgroundMusic => CREATION_BACKGROUND_MUSIC_POINTS_COST,
|
||||
AudioAssetSlot::SoundEffect => CREATION_SOUND_EFFECT_POINTS_COST,
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_audio_task_payload(
|
||||
http_client: &reqwest::Client,
|
||||
settings: &VectorEngineAudioSettings,
|
||||
@@ -1442,6 +1451,28 @@ mod tests {
|
||||
assert!(is_failed_task_status("failed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creation_audio_billing_uses_lower_cost_for_background_music() {
|
||||
let target = AudioAssetBindingTarget {
|
||||
entity_kind: "puzzle_work".to_string(),
|
||||
entity_id: "puzzle-profile-1".to_string(),
|
||||
slot: "background_music".to_string(),
|
||||
asset_kind: "puzzle_background_music".to_string(),
|
||||
profile_id: Some("puzzle-profile-1".to_string()),
|
||||
storage_prefix: LegacyAssetPrefix::PuzzleAssets,
|
||||
storage_scope: "puzzle_work".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
resolve_creation_audio_points_cost(AudioAssetSlot::BackgroundMusic, &target),
|
||||
5
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_creation_audio_points_cost(AudioAssetSlot::SoundEffect, &target),
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_prompt_length() {
|
||||
let prompt = "声".repeat(VIDU_PROMPT_MAX_CHARS + 1);
|
||||
|
||||
Reference in New Issue
Block a user