Warn if scene change frame count does not match (#618)
This commit is contained in:
parent
c8d031c1b8
commit
fc4bf94278
|
@ -1,13 +1,13 @@
|
|||
use ffmpeg::format::Pixel;
|
||||
use itertools::Itertools;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::scenes::Scene;
|
||||
use crate::Encoder;
|
||||
use crate::{into_smallvec, progress_bar, Input, ScenecutMethod, Verbosity};
|
||||
use ansi_term::Style;
|
||||
use anyhow::bail;
|
||||
use av_scenechange::{detect_scene_changes, DetectionOptions, SceneDetectionSpeed};
|
||||
|
||||
use crate::scenes::Scene;
|
||||
use ffmpeg::format::Pixel;
|
||||
use itertools::Itertools;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::io::Read;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::thread;
|
||||
|
||||
|
@ -79,8 +79,109 @@ pub fn scene_detect(
|
|||
sc_downscale_height: Option<usize>,
|
||||
zones: &[Scene],
|
||||
) -> anyhow::Result<Vec<Scene>> {
|
||||
let bit_depth;
|
||||
let (mut decoder, bit_depth) = build_decoder(input, encoder, sc_pix_format, sc_downscale_height)?;
|
||||
|
||||
let mut scenes = Vec::new();
|
||||
let mut cur_zone = zones.first().filter(|frame| frame.start_frame == 0);
|
||||
let mut next_zone_idx = if zones.is_empty() {
|
||||
None
|
||||
} else if cur_zone.is_some() {
|
||||
if zones.len() == 1 {
|
||||
None
|
||||
} else {
|
||||
Some(1)
|
||||
}
|
||||
} else {
|
||||
Some(0)
|
||||
};
|
||||
let mut frames_read = 0;
|
||||
loop {
|
||||
let mut min_scene_len = min_scene_len;
|
||||
if let Some(zone) = cur_zone {
|
||||
if let Some(ref overrides) = zone.zone_overrides {
|
||||
min_scene_len = overrides.min_scene_len;
|
||||
}
|
||||
};
|
||||
let options = DetectionOptions {
|
||||
min_scenecut_distance: Some(min_scene_len),
|
||||
analysis_speed: match sc_method {
|
||||
ScenecutMethod::Fast => SceneDetectionSpeed::Fast,
|
||||
ScenecutMethod::Standard => SceneDetectionSpeed::Standard,
|
||||
},
|
||||
..DetectionOptions::default()
|
||||
};
|
||||
let frame_limit = if let Some(zone) = cur_zone {
|
||||
Some(zone.end_frame - zone.start_frame)
|
||||
} else if let Some(next_idx) = next_zone_idx {
|
||||
let zone = &zones[next_idx];
|
||||
Some(zone.start_frame - frames_read)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let sc_result = if bit_depth > 8 {
|
||||
detect_scene_changes::<_, u16>(&mut decoder, options, frame_limit, callback)
|
||||
} else {
|
||||
detect_scene_changes::<_, u8>(&mut decoder, options, frame_limit, callback)
|
||||
};
|
||||
if let Some(limit) = frame_limit {
|
||||
if limit != sc_result.frame_count {
|
||||
bail!(
|
||||
"Scene change: Expected {} frames but saw {}. This may indicate an issue with the input or filters.",
|
||||
limit,
|
||||
sc_result.frame_count
|
||||
);
|
||||
}
|
||||
}
|
||||
let scene_changes = sc_result.scene_changes;
|
||||
for (start, end) in scene_changes.iter().copied().tuple_windows() {
|
||||
scenes.push(Scene {
|
||||
start_frame: start + frames_read,
|
||||
end_frame: end + frames_read,
|
||||
zone_overrides: cur_zone.and_then(|zone| zone.zone_overrides.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
scenes.push(Scene {
|
||||
start_frame: scenes
|
||||
.last()
|
||||
.map(|scene| scene.end_frame)
|
||||
.unwrap_or_default(),
|
||||
end_frame: if let Some(limit) = frame_limit {
|
||||
frames_read += limit;
|
||||
frames_read
|
||||
} else {
|
||||
total_frames
|
||||
},
|
||||
zone_overrides: cur_zone.and_then(|zone| zone.zone_overrides.clone()),
|
||||
});
|
||||
if let Some(next_idx) = next_zone_idx {
|
||||
if cur_zone.map_or(true, |zone| zone.end_frame == zones[next_idx].start_frame) {
|
||||
cur_zone = Some(&zones[next_idx]);
|
||||
next_zone_idx = if next_idx + 1 == zones.len() {
|
||||
None
|
||||
} else {
|
||||
Some(next_idx + 1)
|
||||
};
|
||||
} else {
|
||||
cur_zone = None;
|
||||
}
|
||||
} else if cur_zone.map_or(true, |zone| zone.end_frame == total_frames) {
|
||||
// End of video
|
||||
break;
|
||||
} else {
|
||||
cur_zone = None;
|
||||
}
|
||||
}
|
||||
Ok(scenes)
|
||||
}
|
||||
|
||||
fn build_decoder(
|
||||
input: &Input,
|
||||
encoder: Encoder,
|
||||
sc_pix_format: Option<Pixel>,
|
||||
sc_downscale_height: Option<usize>,
|
||||
) -> anyhow::Result<(y4m::Decoder<impl Read>, usize)> {
|
||||
let bit_depth;
|
||||
let filters: SmallVec<[String; 4]> = match (sc_downscale_height, sc_pix_format) {
|
||||
(Some(sdh), Some(spf)) => into_smallvec![
|
||||
"-vf",
|
||||
|
@ -95,7 +196,7 @@ pub fn scene_detect(
|
|||
(None, None) => smallvec![],
|
||||
};
|
||||
|
||||
let decoder = &mut y4m::Decoder::new(match input {
|
||||
let decoder = y4m::Decoder::new(match input {
|
||||
Input::VapourSynth(path) => {
|
||||
bit_depth = crate::vapoursynth::bit_depth(path.as_ref())?;
|
||||
let vspipe = Command::new("vspipe")
|
||||
|
@ -142,86 +243,5 @@ pub fn scene_detect(
|
|||
}
|
||||
})?;
|
||||
|
||||
let mut scenes = Vec::new();
|
||||
let mut cur_zone = zones.first().filter(|frame| frame.start_frame == 0);
|
||||
let mut next_zone_idx = if zones.is_empty() {
|
||||
None
|
||||
} else if cur_zone.is_some() {
|
||||
if zones.len() == 1 {
|
||||
None
|
||||
} else {
|
||||
Some(1)
|
||||
}
|
||||
} else {
|
||||
Some(0)
|
||||
};
|
||||
let mut frames_read = 0;
|
||||
loop {
|
||||
let mut min_scene_len = min_scene_len;
|
||||
if let Some(zone) = cur_zone {
|
||||
if let Some(ref overrides) = zone.zone_overrides {
|
||||
min_scene_len = overrides.min_scene_len;
|
||||
}
|
||||
};
|
||||
let options = DetectionOptions {
|
||||
min_scenecut_distance: Some(min_scene_len),
|
||||
analysis_speed: match sc_method {
|
||||
ScenecutMethod::Fast => SceneDetectionSpeed::Fast,
|
||||
ScenecutMethod::Standard => SceneDetectionSpeed::Standard,
|
||||
},
|
||||
..DetectionOptions::default()
|
||||
};
|
||||
let frame_limit = if let Some(zone) = cur_zone {
|
||||
Some(zone.end_frame - zone.start_frame)
|
||||
} else if let Some(next_idx) = next_zone_idx {
|
||||
let zone = &zones[next_idx];
|
||||
Some(zone.start_frame - frames_read)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let scene_changes = if bit_depth > 8 {
|
||||
detect_scene_changes::<_, u16>(decoder, options, frame_limit, callback).scene_changes
|
||||
} else {
|
||||
detect_scene_changes::<_, u8>(decoder, options, frame_limit, callback).scene_changes
|
||||
};
|
||||
for (start, end) in scene_changes.iter().copied().tuple_windows() {
|
||||
scenes.push(Scene {
|
||||
start_frame: start + frames_read,
|
||||
end_frame: end + frames_read,
|
||||
zone_overrides: cur_zone.and_then(|zone| zone.zone_overrides.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
scenes.push(Scene {
|
||||
start_frame: scenes
|
||||
.last()
|
||||
.map(|scene| scene.end_frame)
|
||||
.unwrap_or_default(),
|
||||
end_frame: if let Some(limit) = frame_limit {
|
||||
frames_read += limit;
|
||||
frames_read
|
||||
} else {
|
||||
total_frames
|
||||
},
|
||||
zone_overrides: cur_zone.and_then(|zone| zone.zone_overrides.clone()),
|
||||
});
|
||||
if let Some(next_idx) = next_zone_idx {
|
||||
if cur_zone.map_or(true, |zone| zone.end_frame == zones[next_idx].start_frame) {
|
||||
cur_zone = Some(&zones[next_idx]);
|
||||
next_zone_idx = if next_idx + 1 == zones.len() {
|
||||
None
|
||||
} else {
|
||||
Some(next_idx + 1)
|
||||
};
|
||||
} else {
|
||||
cur_zone = None;
|
||||
}
|
||||
} else if cur_zone.map_or(true, |zone| zone.end_frame == total_frames) {
|
||||
// End of video
|
||||
break;
|
||||
} else {
|
||||
cur_zone = None;
|
||||
}
|
||||
}
|
||||
Ok(scenes)
|
||||
Ok((decoder, bit_depth))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue