use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CaptureMode {
#[default]
Screenshot,
Screencast,
Beginframe,
}
impl std::fmt::Display for CaptureMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Screenshot => write!(f, "screenshot"),
Self::Screencast => write!(f, "screencast"),
Self::Beginframe => write!(f, "beginframe"),
}
}
}
impl std::str::FromStr for CaptureMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"screenshot" => Ok(Self::Screenshot),
"screencast" => Ok(Self::Screencast),
"beginframe" => Ok(Self::Beginframe),
_ => Err(format!("Unknown capture mode: {s}")),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AnimationFormat {
#[default]
Gif,
PngSequence,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimationOptions {
pub max_size: u32,
pub viewport_width: u32,
pub viewport_height: u32,
pub interval: u32,
pub fps: Option<u32>,
pub speed: f64,
pub min_frames: u32,
pub loop_timeout: u32,
pub static_timeout: u32,
pub similarity: f64,
pub crop: bool,
pub capture_mode: CaptureMode,
pub format: AnimationFormat,
pub extract_keyframes: bool,
pub dismiss_popups: bool,
}
impl Default for AnimationOptions {
fn default() -> Self {
Self {
max_size: 1024,
viewport_width: 1920,
viewport_height: 1080,
interval: 0,
fps: None,
speed: 1.0,
min_frames: 120,
loop_timeout: 60,
static_timeout: 60,
similarity: 0.99,
crop: true,
capture_mode: CaptureMode::default(),
format: AnimationFormat::default(),
extract_keyframes: false,
dismiss_popups: true,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Keyframe {
pub index: usize,
#[serde(skip)]
pub buffer: Vec<u8>,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnimationCaptureResult {
#[serde(skip)]
pub frames: Vec<Vec<u8>>,
pub timestamps: Vec<u64>,
pub loop_detected: bool,
pub loop_frame: i64,
pub total_frames: usize,
pub duration: u64,
pub keyframes: Option<Vec<Keyframe>>,
pub width: u32,
pub height: u32,
}
#[must_use]
pub fn compare_frames(frame1: &[u8], frame2: &[u8]) -> f64 {
if frame1.is_empty() || frame2.is_empty() {
return 0.0;
}
if frame1.len() != frame2.len() {
return 0.0;
}
let matching_bytes = frame1
.iter()
.zip(frame2.iter())
.filter(|(a, b)| a == b)
.count();
#[allow(clippy::cast_precision_loss)]
{
matching_bytes as f64 / frame1.len() as f64
}
}
#[allow(clippy::unused_async)]
pub async fn capture_animation_frames(
url: &str,
_options: &AnimationOptions,
) -> crate::Result<AnimationCaptureResult> {
let _absolute_url = if url.starts_with("http") {
url.to_string()
} else {
format!("https://{url}")
};
Err(crate::WebCaptureError::BrowserError(
"Animation capture requires Chrome/Chromium. Install it and enable browser-commander features.".to_string()
))
}