拆分大文件
This commit is contained in:
@@ -735,7 +735,7 @@ async fn persist_animation_preview_video(
|
||||
"provider": "character-animation",
|
||||
"message": "当前策略需要真实生成视频结果,不再支持回退到仓库占位预览视频。",
|
||||
})),
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
let put_result = put_character_animation_object(
|
||||
@@ -1005,7 +1005,9 @@ async fn send_ark_image_to_video_request(
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| map_character_animation_upstream_error(format!("请求 Ark 视频服务失败:{error}")))
|
||||
.map_err(|error| {
|
||||
map_character_animation_upstream_error(format!("请求 Ark 视频服务失败:{error}"))
|
||||
})
|
||||
}
|
||||
|
||||
async fn wait_for_ark_content_generation_task(
|
||||
@@ -1026,7 +1028,9 @@ async fn wait_for_ark_content_generation_task(
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| map_character_animation_upstream_error(format!("查询 Ark 视频任务失败:{error}")))?;
|
||||
.map_err(|error| {
|
||||
map_character_animation_upstream_error(format!("查询 Ark 视频任务失败:{error}"))
|
||||
})?;
|
||||
let status = response.status();
|
||||
let text = response.text().await.map_err(|error| {
|
||||
map_character_animation_upstream_error(format!("读取 Ark 视频任务响应失败:{error}"))
|
||||
@@ -1062,11 +1066,13 @@ async fn wait_for_ark_content_generation_task(
|
||||
sleep(Duration::from_millis(ARK_VIDEO_TASK_POLL_INTERVAL_MS)).await;
|
||||
}
|
||||
|
||||
Err(AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "ark",
|
||||
"message": "视频生成任务执行超时,请稍后重试。",
|
||||
"taskId": task_id,
|
||||
})))
|
||||
Err(
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "ark",
|
||||
"message": "视频生成任务执行超时,请稍后重试。",
|
||||
"taskId": task_id,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
async fn download_generated_video(
|
||||
@@ -1074,11 +1080,9 @@ async fn download_generated_video(
|
||||
video_url: &str,
|
||||
fallback_message: &str,
|
||||
) -> Result<MediaPayload, AppError> {
|
||||
let response = http_client
|
||||
.get(video_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| map_character_animation_upstream_error(format!("{fallback_message}:{error}")))?;
|
||||
let response = http_client.get(video_url).send().await.map_err(|error| {
|
||||
map_character_animation_upstream_error(format!("{fallback_message}:{error}"))
|
||||
})?;
|
||||
let status = response.status();
|
||||
let content_type = response
|
||||
.headers()
|
||||
@@ -1090,11 +1094,13 @@ async fn download_generated_video(
|
||||
map_character_animation_upstream_error(format!("{fallback_message}:{error}"))
|
||||
})?;
|
||||
if !status.is_success() {
|
||||
return Err(AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "character-animation",
|
||||
"message": fallback_message,
|
||||
"status": status.as_u16(),
|
||||
})));
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "character-animation",
|
||||
"message": fallback_message,
|
||||
"status": status.as_u16(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
Ok(MediaPayload {
|
||||
mime_type: content_type.clone(),
|
||||
@@ -1728,12 +1734,9 @@ fn build_character_animation_prompt(
|
||||
loop_,
|
||||
use_chroma_key,
|
||||
),
|
||||
CharacterAnimationStrategy::ImageSequence => build_image_sequence_prompt(
|
||||
animation,
|
||||
prompt_text,
|
||||
frame_count,
|
||||
use_chroma_key,
|
||||
),
|
||||
CharacterAnimationStrategy::ImageSequence => {
|
||||
build_image_sequence_prompt(animation, prompt_text, frame_count, use_chroma_key)
|
||||
}
|
||||
CharacterAnimationStrategy::MotionTransfer
|
||||
| CharacterAnimationStrategy::ReferenceToVideo => build_npc_animation_prompt(
|
||||
animation,
|
||||
@@ -1755,7 +1758,10 @@ fn build_image_sequence_prompt(
|
||||
use_chroma_key: bool,
|
||||
) -> String {
|
||||
[
|
||||
format!("同一角色连续 {} 帧动作序列,动作主题是 {}。", frame_count, animation),
|
||||
format!(
|
||||
"同一角色连续 {} 帧动作序列,动作主题是 {}。",
|
||||
frame_count, animation
|
||||
),
|
||||
"固定机位,单人,全身,侧身朝右,保持同一套服装、发型、武器和体型。".to_string(),
|
||||
"帧间动作连续,姿态逐步推进,不要换人,不要跳变,不要多余物体。".to_string(),
|
||||
if use_chroma_key {
|
||||
@@ -1784,16 +1790,16 @@ fn build_npc_animation_prompt(
|
||||
let character_brief = build_compact_animation_character_brief(character_brief_text);
|
||||
let action_detail_text = sanitize_animation_prompt_text(prompt_text, 140);
|
||||
let loop_rule = if loop_ {
|
||||
"这是循环动作,直接进入动作循环中段,不要开场静止站桩,不要把主参考图原样作为第一帧。".to_string()
|
||||
"这是循环动作,直接进入动作循环中段,不要开场静止站桩,不要把主参考图原样作为第一帧。"
|
||||
.to_string()
|
||||
} else if animation == "die" {
|
||||
"这是死亡终结动作,首帧参考主图角色形象即可,尾帧停在死亡结束姿态,不要回到主图形象。".to_string()
|
||||
"这是死亡终结动作,首帧参考主图角色形象即可,尾帧停在死亡结束姿态,不要回到主图形象。"
|
||||
.to_string()
|
||||
} else {
|
||||
"这是非循环动作,首帧和尾帧都要回到参考主图角色形象,中段完成动作变化。".to_string()
|
||||
};
|
||||
|
||||
if let Some(template) = action_template_id
|
||||
.and_then(|id| find_motion_template(id))
|
||||
{
|
||||
if let Some(template) = action_template_id.and_then(|id| find_motion_template(id)) {
|
||||
return [
|
||||
format!(
|
||||
"单人 NPC 全身动作视频,动作主题是 {}。角色固定为同一人,右向斜侧身,镜头稳定,轮廓清晰,武器不可丢失。",
|
||||
@@ -1843,7 +1849,11 @@ fn build_npc_animation_prompt(
|
||||
} else {
|
||||
action_detail_text
|
||||
},
|
||||
format!("目标帧率 {} fps,时长约 {} 秒。", fps.clamp(1, 60), duration_seconds.clamp(1, 8)),
|
||||
format!(
|
||||
"目标帧率 {} fps,时长约 {} 秒。",
|
||||
fps.clamp(1, 60),
|
||||
duration_seconds.clamp(1, 8)
|
||||
),
|
||||
loop_rule,
|
||||
]
|
||||
.into_iter()
|
||||
@@ -1906,8 +1916,12 @@ fn build_ark_character_animation_prompt(
|
||||
}
|
||||
|
||||
[
|
||||
format!("单人 NPC 全身动作视频,动作英文名是 {}。", normalized_animation_name),
|
||||
"角色固定为图片1和图片2中的同一人,侧身朝右,镜头稳定,轮廓清晰,武器不可丢失。".to_string(),
|
||||
format!(
|
||||
"单人 NPC 全身动作视频,动作英文名是 {}。",
|
||||
normalized_animation_name
|
||||
),
|
||||
"角色固定为图片1和图片2中的同一人,侧身朝右,镜头稳定,轮廓清晰,武器不可丢失。"
|
||||
.to_string(),
|
||||
"动作连贯,避免服装、发型、面部、武器随机漂移,不要多角色,不要镜头切换。".to_string(),
|
||||
if use_chroma_key {
|
||||
"背景为纯绿色绿幕,无其他人物和场景元素,方便后期抠像。".to_string()
|
||||
@@ -2341,11 +2355,7 @@ fn finalize_animation_frame_payload(
|
||||
);
|
||||
}
|
||||
|
||||
let normalized = contain_rgba_image(
|
||||
&image,
|
||||
frame_width.max(1),
|
||||
frame_height.max(1),
|
||||
);
|
||||
let normalized = contain_rgba_image(&image, frame_width.max(1), frame_height.max(1));
|
||||
let mut encoded = Vec::new();
|
||||
let encoder = PngEncoder::new(&mut encoded);
|
||||
encoder
|
||||
@@ -2665,7 +2675,14 @@ fn is_completed_generation_task_status(status: &str) -> bool {
|
||||
fn is_failed_generation_task_status(status: &str) -> bool {
|
||||
matches!(
|
||||
status,
|
||||
"failed" | "canceled" | "cancelled" | "error" | "aborted" | "rejected" | "expired" | "unknown"
|
||||
"failed"
|
||||
| "canceled"
|
||||
| "cancelled"
|
||||
| "error"
|
||||
| "aborted"
|
||||
| "rejected"
|
||||
| "expired"
|
||||
| "unknown"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3110,9 +3127,8 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
}
|
||||
let offset = pixel_index * 4;
|
||||
let alpha = pixels[offset + 3];
|
||||
let strong_candidate = alpha < 40
|
||||
|| green_scores[pixel_index] > 0.12
|
||||
|| white_scores[pixel_index] > 0.32;
|
||||
let strong_candidate =
|
||||
alpha < 40 || green_scores[pixel_index] > 0.12 || white_scores[pixel_index] > 0.32;
|
||||
if !strong_candidate {
|
||||
return;
|
||||
}
|
||||
@@ -3137,9 +3153,21 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
let y = pixel_index / width;
|
||||
let neighbor_indexes = [
|
||||
if x > 0 { Some(pixel_index - 1) } else { None },
|
||||
if x + 1 < width { Some(pixel_index + 1) } else { None },
|
||||
if y > 0 { Some(pixel_index - width) } else { None },
|
||||
if y + 1 < height { Some(pixel_index + width) } else { None },
|
||||
if x + 1 < width {
|
||||
Some(pixel_index + 1)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if y > 0 {
|
||||
Some(pixel_index - width)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if y + 1 < height {
|
||||
Some(pixel_index + width)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
];
|
||||
|
||||
for next_pixel_index in neighbor_indexes.into_iter().flatten() {
|
||||
@@ -3153,9 +3181,7 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
let next_hint = background_hints[next_pixel_index];
|
||||
let reachable_soft_edge = next_hint > 0.08
|
||||
&& next_alpha < SOFT_EDGE_ALPHA_THRESHOLD
|
||||
&& (next_green_score > 0.04
|
||||
|| next_white_score > 0.08
|
||||
|| next_alpha < 180);
|
||||
&& (next_green_score > 0.04 || next_white_score > 0.08 || next_alpha < 180);
|
||||
|
||||
if next_alpha < 40
|
||||
|| next_green_score > 0.12
|
||||
@@ -3203,8 +3229,7 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
}
|
||||
}
|
||||
|
||||
if adjacent_background_count >= 2
|
||||
|| (adjacent_background_count >= 1 && hint > 0.18)
|
||||
if adjacent_background_count >= 2 || (adjacent_background_count >= 1 && hint > 0.18)
|
||||
{
|
||||
expanded_mask[pixel_index] = 1;
|
||||
}
|
||||
@@ -3237,10 +3262,7 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
}
|
||||
let next_x = x as i32 + offset_x;
|
||||
let next_y = y as i32 + offset_y;
|
||||
if next_x < 0
|
||||
|| next_x >= width as i32
|
||||
|| next_y < 0
|
||||
|| next_y >= height as i32
|
||||
if next_x < 0 || next_x >= width as i32 || next_y < 0 || next_y >= height as i32
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -3295,10 +3317,7 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
}
|
||||
let next_x = x as i32 + offset_x;
|
||||
let next_y = y as i32 + offset_y;
|
||||
if next_x < 0
|
||||
|| next_x >= width as i32
|
||||
|| next_y < 0
|
||||
|| next_y >= height as i32
|
||||
if next_x < 0 || next_x >= width as i32 || next_y < 0 || next_y >= height as i32
|
||||
{
|
||||
touches_transparent_edge = true;
|
||||
continue;
|
||||
@@ -3320,7 +3339,11 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
let white_score = white_scores[pixel_index];
|
||||
let contamination = green_score
|
||||
.max(white_score)
|
||||
.max(if background_mask[pixel_index] != 0 { 0.35 } else { 0.0 })
|
||||
.max(if background_mask[pixel_index] != 0 {
|
||||
0.35
|
||||
} else {
|
||||
0.0
|
||||
})
|
||||
.max(if alpha < 220 {
|
||||
((220 - alpha) as f32 / 220.0) * 0.25
|
||||
} else {
|
||||
@@ -3343,7 +3366,8 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
&background_mask,
|
||||
&background_hints,
|
||||
);
|
||||
let blend = clamp01(contamination.max(if touches_transparent_edge { 0.22 } else { 0.0 }));
|
||||
let blend =
|
||||
clamp01(contamination.max(if touches_transparent_edge { 0.22 } else { 0.0 }));
|
||||
|
||||
if let Some((sample_red, sample_green, sample_blue)) = sample {
|
||||
red = lerp(red, sample_red as f32, blend);
|
||||
@@ -3360,7 +3384,8 @@ fn remove_background_from_rgba(pixels: &mut [u8], width: usize, height: usize) -
|
||||
}
|
||||
} else {
|
||||
if green_score > 0.04 {
|
||||
green = green.max(red.max(blue))
|
||||
green = green
|
||||
.max(red.max(blue))
|
||||
.max((green - (green - red.max(blue)) * 0.78).round());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user