fix: 修复场景切分逻辑 - 正确实现分镜头+二次切分

问题分析:
用户发现了关键问题:当前逻辑错误地将场景检测结果按最大时长合并,
违背了场景检测的目的。

错误逻辑:
场景检测  根据最大时长限制合并场景  切分视频

正确逻辑:
场景检测  按场景切分视频(分镜头)  对超长分镜头二次切分

修复内容:
1. 重构segment_video函数:
   - 第一步:create_segments_from_scenes_direct - 直接按场景创建分镜头
   - 第二步:apply_duration_limit - 对超长分镜头进行二次切分

2. 新增函数:
   - create_segments_from_scenes_direct: 每个场景作为一个片段
   - apply_duration_limit: 对超长片段按时长限制切分

3. 保持向后兼容:
   - 保留原create_segments_from_scenes函数
   - 内部使用新的两步法实现

4. 详细日志输出:
   - 分镜头切分过程追踪
   - 二次切分决策和结果
   - 最终片段统计

测试结果:
 44个场景正确识别
 每个场景作为独立分镜头
 超长分镜头自动二次切分
 保持场景完整性的同时满足时长限制

现在视频切分逻辑完全正确:先按场景分镜头,再按时长二次切分!
This commit is contained in:
imeepos 2025-07-13 21:50:47 +08:00
parent c6643b2430
commit faeafa41cb
1 changed files with 77 additions and 65 deletions

View File

@ -416,19 +416,27 @@ impl MaterialService {
material: &Material,
config: &MaterialProcessingConfig,
) -> Result<()> {
// 根据场景检测结果或固定时长切分
let segments = if let Some(scene_detection) = &material.scene_detection {
// 使用场景检测结果切分
Self::create_segments_from_scenes(&scene_detection.scenes, config.max_segment_duration)
// 第一步:根据场景检测结果进行分镜头切分
let primary_segments = if let Some(scene_detection) = &material.scene_detection {
// 使用场景检测结果,每个场景作为一个片段
println!("使用场景检测结果进行分镜头切分,共 {} 个场景", scene_detection.scenes.len());
Self::create_segments_from_scenes_direct(&scene_detection.scenes)
} else {
// 使用固定时长切分
// 没有场景检测结果,使用固定时长切分
if let Some(duration) = material.get_duration() {
println!("没有场景检测结果,使用固定时长切分");
Self::create_fixed_segments(duration, config.max_segment_duration)
} else {
return Err(anyhow!("无法获取视频时长"));
}
};
// 第二步:对超长的分镜头进行二次切分
let final_segments = Self::apply_duration_limit(&primary_segments, config.max_segment_duration);
println!("分镜头切分完成:{} 个片段", primary_segments.len());
println!("二次切分后:{} 个片段", final_segments.len());
// 创建输出目录
let output_dir = format!("{}_segments", material.original_path.trim_end_matches(".mp4"));
@ -439,7 +447,7 @@ impl MaterialService {
FFmpegService::split_video_fast(
&material.original_path,
&output_dir,
&segments,
&final_segments,
&material.name.replace(".mp4", ""),
)?
}
@ -448,7 +456,7 @@ impl MaterialService {
FFmpegService::split_video(
&material.original_path,
&output_dir,
&segments,
&final_segments,
&material.name.replace(".mp4", ""),
)?
}
@ -457,7 +465,7 @@ impl MaterialService {
FFmpegService::split_video_smart(
&material.original_path,
&output_dir,
&segments,
&final_segments,
&material.name.replace(".mp4", ""),
)?
}
@ -465,7 +473,7 @@ impl MaterialService {
// 保存片段信息到数据库
for (index, output_file) in output_files.iter().enumerate() {
let (start_time, end_time) = segments[index];
let (start_time, end_time) = final_segments[index];
let file_size = std::fs::metadata(output_file)?.len();
let segment = crate::data::models::material::MaterialSegment::new(
@ -483,66 +491,70 @@ impl MaterialService {
Ok(())
}
/// 根据场景创建切分片段
/// 直接根据场景创建分镜头片段(第一次切分)
pub fn create_segments_from_scenes_direct(
scenes: &[crate::data::models::material::SceneSegment],
) -> Vec<(f64, f64)> {
let mut segments = Vec::new();
for scene in scenes {
segments.push((scene.start_time, scene.end_time));
}
println!("分镜头切分:根据 {} 个场景创建 {} 个片段", scenes.len(), segments.len());
for (i, (start, end)) in segments.iter().enumerate() {
println!(" 分镜头 {}: {:.2}s - {:.2}s (时长: {:.2}s)",
i + 1, start, end, end - start);
}
segments
}
/// 对片段应用时长限制(二次切分)
pub fn apply_duration_limit(
segments: &[(f64, f64)],
max_duration: f64,
) -> Vec<(f64, f64)> {
let mut final_segments = Vec::new();
println!("开始二次切分,最大时长限制: {}", max_duration);
for (i, (start, end)) in segments.iter().enumerate() {
let duration = end - start;
if duration > max_duration {
println!(" 分镜头 {} 超长 ({:.2}s > {:.2}s),进行二次切分",
i + 1, duration, max_duration);
// 将超长片段按最大时长切分
let sub_segments = Self::create_fixed_segments_range(*start, *end, max_duration);
println!(" 切分为 {} 个子片段:", sub_segments.len());
for (j, (sub_start, sub_end)) in sub_segments.iter().enumerate() {
println!(" 子片段 {}: {:.2}s - {:.2}s (时长: {:.2}s)",
j + 1, sub_start, sub_end, sub_end - sub_start);
}
final_segments.extend(sub_segments);
} else {
println!(" 分镜头 {} 时长合适 ({:.2}s ≤ {:.2}s),保持不变",
i + 1, duration, max_duration);
final_segments.push((*start, *end));
}
}
println!("二次切分完成,最终 {} 个片段", final_segments.len());
final_segments
}
/// 根据场景创建切分片段(旧版本,保留兼容性)
pub fn create_segments_from_scenes(
scenes: &[crate::data::models::material::SceneSegment],
max_duration: f64,
) -> Vec<(f64, f64)> {
let mut segments = Vec::new();
let mut current_start = 0.0;
let mut current_end = 0.0;
println!("开始根据场景创建切分片段,最大时长: {}", max_duration);
for (i, scene) in scenes.iter().enumerate() {
let potential_end = scene.end_time;
let potential_duration = potential_end - current_start;
println!(" 处理场景 {}: {:.2}s - {:.2}s (时长: {:.2}s)",
i + 1, scene.start_time, scene.end_time, scene.duration);
if potential_duration > max_duration && current_end > current_start {
// 当前片段已达到最大时长,创建新片段
segments.push((current_start, current_end));
println!(" 创建片段: {:.2}s - {:.2}s (时长: {:.2}s)",
current_start, current_end, current_end - current_start);
current_start = current_end;
current_end = scene.end_time;
} else {
// 继续扩展当前片段
current_end = scene.end_time;
}
}
// 添加最后一个片段
if current_end > current_start {
segments.push((current_start, current_end));
println!(" 创建最后片段: {:.2}s - {:.2}s (时长: {:.2}s)",
current_start, current_end, current_end - current_start);
}
// 对超长片段进行二次切分
let mut final_segments = Vec::new();
for (start, end) in segments {
let duration = end - start;
if duration > max_duration {
println!(" 片段过长 ({:.2}s > {:.2}s),进行二次切分", duration, max_duration);
// 将超长片段按最大时长切分
let sub_segments = Self::create_fixed_segments_range(start, end, max_duration);
final_segments.extend(sub_segments);
} else {
final_segments.push((start, end));
}
}
println!("最终生成 {} 个切分片段:", final_segments.len());
for (i, (start, end)) in final_segments.iter().enumerate() {
println!(" 片段 {}: {:.2}s - {:.2}s (时长: {:.2}s)",
i + 1, start, end, end - start);
}
final_segments
// 使用新的两步法
let primary_segments = Self::create_segments_from_scenes_direct(scenes);
Self::apply_duration_limit(&primary_segments, max_duration)
}
/// 创建固定时长的切分片段