1use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum CaptureMode {
24 #[default]
26 Screenshot,
27 Screencast,
29 Beginframe,
31}
32
33impl std::fmt::Display for CaptureMode {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 Self::Screenshot => write!(f, "screenshot"),
37 Self::Screencast => write!(f, "screencast"),
38 Self::Beginframe => write!(f, "beginframe"),
39 }
40 }
41}
42
43impl std::str::FromStr for CaptureMode {
44 type Err = String;
45
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
47 match s.to_lowercase().as_str() {
48 "screenshot" => Ok(Self::Screenshot),
49 "screencast" => Ok(Self::Screencast),
50 "beginframe" => Ok(Self::Beginframe),
51 _ => Err(format!("Unknown capture mode: {s}")),
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(rename_all = "lowercase")]
59pub enum AnimationFormat {
60 #[default]
61 Gif,
62 PngSequence,
63}
64
65#[allow(clippy::struct_excessive_bools)]
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct AnimationOptions {
69 pub max_size: u32,
70 pub viewport_width: u32,
71 pub viewport_height: u32,
72 pub interval: u32,
73 pub fps: Option<u32>,
74 pub speed: f64,
75 pub min_frames: u32,
76 pub loop_timeout: u32,
77 pub static_timeout: u32,
78 pub similarity: f64,
79 pub crop: bool,
80 pub capture_mode: CaptureMode,
81 pub format: AnimationFormat,
82 pub extract_keyframes: bool,
83 pub dismiss_popups: bool,
84}
85
86impl Default for AnimationOptions {
87 fn default() -> Self {
88 Self {
89 max_size: 1024,
90 viewport_width: 1920,
91 viewport_height: 1080,
92 interval: 0,
93 fps: None,
94 speed: 1.0,
95 min_frames: 120,
96 loop_timeout: 60,
97 static_timeout: 60,
98 similarity: 0.99,
99 crop: true,
100 capture_mode: CaptureMode::default(),
101 format: AnimationFormat::default(),
102 extract_keyframes: false,
103 dismiss_popups: true,
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize)]
110pub struct Keyframe {
111 pub index: usize,
112 #[serde(skip)]
113 pub buffer: Vec<u8>,
114}
115
116#[derive(Debug, Clone, Serialize)]
118pub struct AnimationCaptureResult {
119 #[serde(skip)]
120 pub frames: Vec<Vec<u8>>,
121 pub timestamps: Vec<u64>,
122 pub loop_detected: bool,
123 pub loop_frame: i64,
124 pub total_frames: usize,
125 pub duration: u64,
126 pub keyframes: Option<Vec<Keyframe>>,
127 pub width: u32,
128 pub height: u32,
129}
130
131#[must_use]
135pub fn compare_frames(frame1: &[u8], frame2: &[u8]) -> f64 {
136 if frame1.is_empty() || frame2.is_empty() {
137 return 0.0;
138 }
139 if frame1.len() != frame2.len() {
140 return 0.0;
141 }
142
143 let matching_bytes = frame1
144 .iter()
145 .zip(frame2.iter())
146 .filter(|(a, b)| a == b)
147 .count();
148
149 #[allow(clippy::cast_precision_loss)]
150 {
151 matching_bytes as f64 / frame1.len() as f64
152 }
153}
154
155#[allow(clippy::unused_async)]
160pub async fn capture_animation_frames(
161 url: &str,
162 _options: &AnimationOptions,
163) -> crate::Result<AnimationCaptureResult> {
164 let _absolute_url = if url.starts_with("http") {
165 url.to_string()
166 } else {
167 format!("https://{url}")
168 };
169
170 Err(crate::WebCaptureError::BrowserError(
172 "Animation capture requires Chrome/Chromium. Install it and enable browser-commander features.".to_string()
173 ))
174}