lookup characters in seedance

This commit is contained in:
Brandon Thomas committed 2026-04-02 21:30:34 -04:00
1 parent b0f96ea5bf
commit 519d30e3b0
5 files changed
+145 -1

No files matched your search

@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use tokens::tokens::characters::CharacterToken;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -29,6 +30,10 @@ pub struct Seedance2p0MultiFunctionVideoGenRequest {
/// Optional reference audio files.
pub reference_audio_media_tokens: Option<Vec<MediaFileToken>>,
/// Optional character tokens to reference in the prompt.
/// Characters are referenced in prompts as @CharacterName.
pub reference_character_tokens: Option<Vec<CharacterToken>>,
/// Video aspect ratio / resolution.
pub aspect_ratio: Option<Seedance2p0AspectRatio>,
@@ -23,6 +23,7 @@ pub async fn execute_artcraft_seedance2p0(
aspect_ratio: plan.aspect_ratio,
duration_seconds: plan.duration_seconds,
batch_count: Some(plan.batch_count),
reference_character_tokens: None,
};
let response = seedance_2p0_multi_function_video_gen(
@@ -0,0 +1,82 @@
use anyhow::anyhow;
use log::warn;
use sqlx::pool::PoolConnection;
use sqlx::MySql;
use tokens::tokens::characters::CharacterToken;
/// Character data needed for constructing prompts with character references.
#[derive(Debug)]
pub struct CharacterPromptData {
pub character_token: CharacterToken,
pub is_active: bool,
pub character_name: Option<String>,
pub kinovi_character_id: Option<String>,
pub kinovi_character_name: Option<String>,
}
/// Look up multiple characters by their tokens, returning the data needed for prompting.
///
/// Characters that are not found are silently omitted from the results.
pub async fn batch_lookup_characters_by_token_for_prompting(
tokens: &[CharacterToken],
connection: &mut PoolConnection<MySql>,
) -> anyhow::Result<Vec<CharacterPromptData>> {
if tokens.is_empty() {
return Ok(Vec::new());
}
// SQLx doesn't support IN-clause with dynamic lists in query! macro,
// so we build the query manually with placeholders.
let placeholders: Vec<&str> = tokens.iter().map(|_| "?").collect();
let in_clause = placeholders.join(", ");
let sql = format!(
r#"
SELECT
token,
is_active,
character_name,
kinovi_character_id,
kinovi_character_name
FROM characters
WHERE token IN ({})
AND deleted_at IS NULL
"#,
in_clause,
);
let mut query = sqlx::query_as::<_, RawRow>(&sql);
for token in tokens {
query = query.bind(token.as_str());
}
let rows = query
.fetch_all(&mut **connection)
.await
.map_err(|err| anyhow!("Error looking up characters by token: {:?}", err))?;
let results = rows.into_iter().map(|row| {
CharacterPromptData {
character_token: row.token,
is_active: row.is_active,
character_name: row.character_name,
kinovi_character_id: row.kinovi_character_id,
kinovi_character_name: row.kinovi_character_name,
}
}).collect();
Ok(results)
}
// =============== Private helpers ===============
#[derive(sqlx::FromRow)]
struct RawRow {
token: CharacterToken,
is_active: bool,
character_name: Option<String>,
kinovi_character_id: Option<String>,
kinovi_character_name: Option<String>,
}
@@ -1 +1,2 @@
pub mod batch_lookup_characters_by_token_for_prompting;
pub mod create_pending_character;
@@ -25,6 +25,7 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::visibility::Visibility;
use http_server_common::request::get_request_ip::get_request_ip;
use log::{error, info, warn};
use mysql_queries::queries::characters::batch_lookup_characters_by_token_for_prompting::batch_lookup_characters_by_token_for_prompting;
use mysql_queries::queries::generic_inference::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::{
insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token,
InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs,
@@ -142,6 +143,56 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
).await?
};
// --- Look up character tokens (if any) ---
let kinovi_character_ids: Option<Vec<String>> = if let Some(character_tokens) = request.reference_character_tokens.as_ref() {
if !character_tokens.is_empty() {
let characters = batch_lookup_characters_by_token_for_prompting(
character_tokens,
&mut mysql_connection,
)
.await
.map_err(|err| {
error!("Error looking up characters: {:?}", err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
// Verify all requested characters were found.
if characters.len() != character_tokens.len() {
warn!("Not all character tokens were found: requested {}, found {}", character_tokens.len(), characters.len());
return Err(AdvancedCommonWebError::BadInputWithSimpleMessage(
"One or more character tokens were not found.".to_string()
));
}
// Verify all characters are active.
for character in &characters {
if !character.is_active {
warn!("Character {} is not active", character.character_token);
return Err(AdvancedCommonWebError::BadInputWithSimpleMessage(
format!("Character {} is not yet ready for use.", character.character_token)
));
}
}
// Extract the kinovi character IDs.
let ids: Vec<String> = characters.iter()
.filter_map(|c| c.kinovi_character_id.clone())
.collect();
if ids.is_empty() {
warn!("Characters found but none have kinovi_character_id");
None
} else {
Some(ids)
}
} else {
None
}
} else {
None
};
// --- Insert idempotency token ---
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
@@ -191,6 +242,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
resolution,
batch_count,
duration_seconds,
kinovi_character_ids.clone(),
).await;
match result {
@@ -212,6 +264,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
resolution,
batch_count,
duration_seconds,
kinovi_character_ids.clone(),
).await;
match result {
@@ -248,6 +301,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
resolution,
batch_count,
duration_seconds,
kinovi_character_ids,
).await;
match result {
@@ -496,6 +550,7 @@ async fn upload_and_generate(
resolution: Resolution,
batch_count: BatchCount,
duration_seconds: u8,
kinovi_character_ids: Option<Vec<String>>,
) -> Result<SeedanceGenerationResult, AdvancedCommonWebError> {
// --- Upload files to seedance2pro CDN ---
@@ -570,7 +625,7 @@ async fn upload_and_generate(
reference_image_urls,
reference_video_urls,
reference_audio_urls,
character_ids: None,
character_ids: kinovi_character_ids,
use_face_blur_hack: None,
host_override: None,
};