1#![allow(missing_docs)]
6
7use oximedia_core::Rational;
8use oximedia_graph::FilterGraph;
9use std::collections::BTreeMap;
10
11use crate::error::{EditError, EditResult};
12
13#[derive(Clone, Debug, Default)]
15pub struct EffectStack {
16 pub effects: Vec<Effect>,
18}
19
20impl EffectStack {
21 #[must_use]
23 pub fn new() -> Self {
24 Self {
25 effects: Vec::new(),
26 }
27 }
28
29 pub fn add(&mut self, effect: Effect) {
31 self.effects.push(effect);
32 }
33
34 pub fn remove(&mut self, index: usize) -> Option<Effect> {
36 if index < self.effects.len() {
37 Some(self.effects.remove(index))
38 } else {
39 None
40 }
41 }
42
43 #[must_use]
45 pub fn get(&self, index: usize) -> Option<&Effect> {
46 self.effects.get(index)
47 }
48
49 pub fn get_mut(&mut self, index: usize) -> Option<&mut Effect> {
51 self.effects.get_mut(index)
52 }
53
54 #[must_use]
56 pub fn len(&self) -> usize {
57 self.effects.len()
58 }
59
60 #[must_use]
62 pub fn is_empty(&self) -> bool {
63 self.effects.is_empty()
64 }
65
66 pub fn clear(&mut self) {
68 self.effects.clear();
69 }
70
71 pub fn evaluate_at(&self, time: i64, timebase: Rational) -> EditResult<Vec<EffectInstance>> {
73 self.effects
74 .iter()
75 .map(|effect| effect.evaluate_at(time, timebase))
76 .collect()
77 }
78}
79
80#[derive(Clone, Debug)]
82pub struct Effect {
83 pub effect_type: EffectType,
85 pub parameters: BTreeMap<String, Parameter>,
87 pub enabled: bool,
89 pub name: Option<String>,
91}
92
93impl Effect {
94 #[must_use]
96 pub fn new(effect_type: EffectType) -> Self {
97 Self {
98 effect_type,
99 parameters: BTreeMap::new(),
100 enabled: true,
101 name: None,
102 }
103 }
104
105 pub fn set_parameter(&mut self, name: String, parameter: Parameter) {
107 self.parameters.insert(name, parameter);
108 }
109
110 #[must_use]
112 pub fn get_parameter(&self, name: &str) -> Option<&Parameter> {
113 self.parameters.get(name)
114 }
115
116 pub fn evaluate_at(&self, time: i64, timebase: Rational) -> EditResult<EffectInstance> {
118 let mut values = BTreeMap::new();
119
120 for (name, param) in &self.parameters {
121 let value = param.evaluate_at(time, timebase)?;
122 values.insert(name.clone(), value);
123 }
124
125 Ok(EffectInstance {
126 effect_type: self.effect_type.clone(),
127 values,
128 })
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Eq)]
134pub enum EffectType {
135 Brightness,
137 Contrast,
139 Saturation,
141 Hue,
143 Blur,
145 Sharpen,
147 ColorCurve,
149 Crop,
151 Scale,
153 Rotate,
155 Position,
157 Opacity,
159 AudioGain,
161 AudioPan,
163 AudioEq,
165 AudioReverb,
167 ChromaKey,
169 Custom(String),
171}
172
173#[derive(Clone, Debug)]
175pub struct Parameter {
176 pub keyframes: BTreeMap<i64, ParameterValue>,
178 pub interpolation: InterpolationMode,
180}
181
182impl Parameter {
183 #[must_use]
185 pub fn constant(value: ParameterValue) -> Self {
186 let mut keyframes = BTreeMap::new();
187 keyframes.insert(0, value);
188 Self {
189 keyframes,
190 interpolation: InterpolationMode::Linear,
191 }
192 }
193
194 pub fn add_keyframe(&mut self, time: i64, value: ParameterValue) {
196 self.keyframes.insert(time, value);
197 }
198
199 pub fn remove_keyframe(&mut self, time: i64) -> Option<ParameterValue> {
201 self.keyframes.remove(&time)
202 }
203
204 pub fn evaluate_at(&self, time: i64, _timebase: Rational) -> EditResult<ParameterValue> {
206 if self.keyframes.is_empty() {
207 return Err(EditError::KeyframeError("No keyframes defined".to_string()));
208 }
209
210 if let Some(value) = self.keyframes.get(&time) {
212 return Ok(value.clone());
213 }
214
215 let before = self
217 .keyframes
218 .range(..time)
219 .next_back()
220 .map(|(t, v)| (*t, v.clone()));
221 let after = self
222 .keyframes
223 .range(time..)
224 .next()
225 .map(|(t, v)| (*t, v.clone()));
226
227 match (before, after) {
228 (Some((t1, v1)), Some((t2, v2))) => {
229 let factor = if t2 == t1 {
231 0.0
232 } else {
233 #[allow(clippy::cast_precision_loss)]
234 let result = (time - t1) as f64 / (t2 - t1) as f64;
235 result
236 };
237 Ok(self.interpolation.interpolate(&v1, &v2, factor))
238 }
239 (Some((_, v)), None) | (None, Some((_, v))) => {
240 Ok(v)
242 }
243 (None, None) => Err(EditError::KeyframeError("No keyframes found".to_string())),
244 }
245 }
246
247 #[must_use]
249 pub fn keyframe_times(&self) -> Vec<i64> {
250 self.keyframes.keys().copied().collect()
251 }
252}
253
254#[derive(Clone, Debug)]
256pub enum ParameterValue {
257 Float(f64),
259 Int(i64),
261 Bool(bool),
263 String(String),
265 Point2D { x: f64, y: f64 },
267 Point3D { x: f64, y: f64, z: f64 },
269 Color { r: f32, g: f32, b: f32, a: f32 },
271}
272
273#[derive(Clone, Copy, Debug, PartialEq, Eq)]
275pub enum InterpolationMode {
276 Constant,
278 Linear,
280 Smooth,
282 Bezier,
284}
285
286impl InterpolationMode {
287 #[must_use]
289 #[allow(clippy::needless_pass_by_value)]
290 pub fn interpolate(
291 &self,
292 v1: &ParameterValue,
293 v2: &ParameterValue,
294 factor: f64,
295 ) -> ParameterValue {
296 let t = match self {
297 Self::Constant => 0.0,
298 Self::Linear => factor,
299 Self::Smooth => {
300 factor * factor * (3.0 - 2.0 * factor)
302 }
303 Self::Bezier => {
304 let t = factor;
306 t * t * (3.0 - 2.0 * t)
307 }
308 };
309
310 match (v1, v2) {
311 (ParameterValue::Float(a), ParameterValue::Float(b)) => {
312 ParameterValue::Float(a + (b - a) * t)
313 }
314 (ParameterValue::Int(a), ParameterValue::Int(b)) => {
315 #[allow(clippy::cast_possible_truncation)]
316 let result = a + ((b - a) as f64 * t) as i64;
317 ParameterValue::Int(result)
318 }
319 (ParameterValue::Bool(a), ParameterValue::Bool(_)) => {
320 ParameterValue::Bool(*a)
322 }
323 (ParameterValue::String(s), _) => {
324 ParameterValue::String(s.clone())
326 }
327 (
328 ParameterValue::Point2D { x: x1, y: y1 },
329 ParameterValue::Point2D { x: x2, y: y2 },
330 ) => ParameterValue::Point2D {
331 x: x1 + (x2 - x1) * t,
332 y: y1 + (y2 - y1) * t,
333 },
334 (
335 ParameterValue::Point3D {
336 x: x1,
337 y: y1,
338 z: z1,
339 },
340 ParameterValue::Point3D {
341 x: x2,
342 y: y2,
343 z: z2,
344 },
345 ) => ParameterValue::Point3D {
346 x: x1 + (x2 - x1) * t,
347 y: y1 + (y2 - y1) * t,
348 z: z1 + (z2 - z1) * t,
349 },
350 (
351 ParameterValue::Color {
352 r: r1,
353 g: g1,
354 b: b1,
355 a: a1,
356 },
357 ParameterValue::Color {
358 r: r2,
359 g: g2,
360 b: b2,
361 a: a2,
362 },
363 ) => {
364 #[allow(clippy::cast_possible_truncation)]
365 let result = ParameterValue::Color {
366 r: r1 + (r2 - r1) * t as f32,
367 g: g1 + (g2 - g1) * t as f32,
368 b: b1 + (b2 - b1) * t as f32,
369 a: a1 + (a2 - a1) * t as f32,
370 };
371 result
372 }
373 _ => {
374 v1.clone()
376 }
377 }
378 }
379}
380
381#[derive(Clone, Debug)]
383pub struct EffectInstance {
384 pub effect_type: EffectType,
386 pub values: BTreeMap<String, ParameterValue>,
388}
389
390impl EffectInstance {
391 #[must_use]
393 pub fn get_float(&self, name: &str) -> Option<f64> {
394 match self.values.get(name) {
395 Some(ParameterValue::Float(v)) => Some(*v),
396 _ => None,
397 }
398 }
399
400 #[must_use]
402 pub fn get_int(&self, name: &str) -> Option<i64> {
403 match self.values.get(name) {
404 Some(ParameterValue::Int(v)) => Some(*v),
405 _ => None,
406 }
407 }
408
409 #[must_use]
411 pub fn get_bool(&self, name: &str) -> Option<bool> {
412 match self.values.get(name) {
413 Some(ParameterValue::Bool(v)) => Some(*v),
414 _ => None,
415 }
416 }
417
418 #[must_use]
420 pub fn get_point2d(&self, name: &str) -> Option<(f64, f64)> {
421 match self.values.get(name) {
422 Some(ParameterValue::Point2D { x, y }) => Some((*x, *y)),
423 _ => None,
424 }
425 }
426}
427
428#[derive(Clone, Debug)]
430pub struct EffectPreset {
431 pub name: String,
433 pub description: String,
435 pub effect: Effect,
437}
438
439impl EffectPreset {
440 #[must_use]
442 pub fn new(name: String, description: String, effect: Effect) -> Self {
443 Self {
444 name,
445 description,
446 effect,
447 }
448 }
449
450 #[must_use]
452 pub fn brightness(value: f64) -> Self {
453 let mut effect = Effect::new(EffectType::Brightness);
454 effect.set_parameter(
455 "brightness".to_string(),
456 Parameter::constant(ParameterValue::Float(value)),
457 );
458 Self::new(
459 "Brightness".to_string(),
460 format!("Adjust brightness to {value}"),
461 effect,
462 )
463 }
464
465 #[must_use]
467 pub fn blur(radius: f64) -> Self {
468 let mut effect = Effect::new(EffectType::Blur);
469 effect.set_parameter(
470 "radius".to_string(),
471 Parameter::constant(ParameterValue::Float(radius)),
472 );
473 Self::new(
474 "Blur".to_string(),
475 format!("Apply blur with radius {radius}"),
476 effect,
477 )
478 }
479
480 #[must_use]
482 pub fn fade_in(duration: i64) -> Self {
483 let mut effect = Effect::new(EffectType::Opacity);
484 let mut parameter = Parameter::constant(ParameterValue::Float(0.0));
485 parameter.add_keyframe(0, ParameterValue::Float(0.0));
486 parameter.add_keyframe(duration, ParameterValue::Float(1.0));
487 parameter.interpolation = InterpolationMode::Linear;
488 effect.set_parameter("opacity".to_string(), parameter);
489 Self::new(
490 "Fade In".to_string(),
491 format!("Fade in over {duration} frames"),
492 effect,
493 )
494 }
495
496 #[must_use]
498 pub fn fade_out(duration: i64) -> Self {
499 let mut effect = Effect::new(EffectType::Opacity);
500 let mut parameter = Parameter::constant(ParameterValue::Float(1.0));
501 parameter.add_keyframe(0, ParameterValue::Float(1.0));
502 parameter.add_keyframe(duration, ParameterValue::Float(0.0));
503 parameter.interpolation = InterpolationMode::Linear;
504 effect.set_parameter("opacity".to_string(), parameter);
505 Self::new(
506 "Fade Out".to_string(),
507 format!("Fade out over {duration} frames"),
508 effect,
509 )
510 }
511}
512
513#[derive(Debug, Default)]
515pub struct EffectGraphBuilder;
516
517impl EffectGraphBuilder {
518 #[must_use]
520 pub fn new() -> Self {
521 Self
522 }
523
524 pub fn build(&self, _effects: &EffectStack) -> EditResult<FilterGraph> {
526 Ok(FilterGraph::new())
529 }
530}