mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-08 11:01:35 +00:00
lookup characters in seedance
This commit is contained in:
1 parent
b0f96ea5bf
commit
519d30e3b0
5 files changed
+145
-1
No files matched your search
+5
@@ -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>,
|
||||
|
||||
|
||||
+1
@@ -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(
|
||||
|
||||
+82
@@ -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;
|
||||
+56
-1
@@ -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,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user