1use std::collections::BTreeMap;
11
12use alpha2_model::{
13 Animated, Composition, Easing, EffectValue, LayoutNode, MediaSource, Rgba, Track,
14};
15use serde::{Deserialize, Serialize};
16
17pub const COMPOSE_VERSION: u32 = 1;
19
20#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23#[serde(deny_unknown_fields)]
24pub struct VideoSpec {
25 #[serde(default = "compose_version")]
27 #[cfg_attr(feature = "schema", schemars(extend("const" = COMPOSE_VERSION)))]
28 pub compose_version: u32,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub name: Option<String>,
32 #[serde(default)]
34 pub canvas: Canvas,
35 #[serde(default)]
37 pub theme: ThemeChoice,
38 pub body: Node,
41 #[serde(default, skip_serializing_if = "Vec::is_empty")]
44 pub overlays: Vec<Node>,
45 #[serde(default, skip_serializing_if = "Vec::is_empty")]
48 pub audio: Vec<Node>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub duration: Option<f64>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub loudness: Option<Loudness>,
56}
57
58const fn compose_version() -> u32 {
59 COMPOSE_VERSION
60}
61
62#[derive(Clone, Debug, PartialEq, Serialize)]
64#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
65#[serde(untagged)]
66pub enum Canvas {
67 Preset(String),
71 Custom(CustomCanvas),
73}
74
75impl Default for Canvas {
76 fn default() -> Self {
77 Self::Preset("youtube-1080p".to_owned())
78 }
79}
80
81#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
83#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
84#[serde(deny_unknown_fields)]
85pub struct CustomCanvas {
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub preset: Option<String>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub width: Option<u32>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub height: Option<u32>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub frame_rate: Option<FrameRateChoice>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub background: Option<Rgba>,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
106#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
107#[serde(untagged)]
108pub enum FrameRateChoice {
109 Number(f64),
112 Exact(alpha2_time::FrameRate),
114}
115
116#[derive(Clone, Debug, PartialEq, Serialize)]
118#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
119#[serde(untagged)]
120pub enum ThemeChoice {
121 Named(String),
123 Custom(Box<Theme>),
125}
126
127impl Default for ThemeChoice {
128 fn default() -> Self {
129 Self::Named("clean-dark".to_owned())
130 }
131}
132
133#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
137#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
138#[serde(deny_unknown_fields)]
139pub struct Theme {
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub base: Option<String>,
143 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub font_family: Option<String>,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub text: Option<Rgba>,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub background: Option<Rgba>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub surface: Option<Rgba>,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub panel: Option<Rgba>,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub accent: Option<Rgba>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub title_size: Option<f32>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub subtitle_size: Option<f32>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub body_size: Option<f32>,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub caption_size: Option<f32>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub label_size: Option<f32>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub margin: Option<f32>,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub transition: Option<Transition>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub still_seconds: Option<f64>,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub text_seconds: Option<f64>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub fade_seconds: Option<f64>,
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
199#[serde(untagged)]
200pub enum Loudness {
201 Lufs(f64),
203 Target(crate::LoudnessTarget),
205}
206
207#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
209#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
210#[serde(deny_unknown_fields)]
211pub struct Transition {
212 #[serde(rename = "type")]
215 pub kind: String,
216 pub duration: f64,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub easing: Option<Easing>,
221 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
223 pub params: BTreeMap<String, Animated<EffectValue>>,
224 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub version: Option<u32>,
227}
228
229#[derive(Clone, Debug, PartialEq, Serialize)]
231#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
232#[serde(untagged)]
233pub enum TransitionChoice {
234 Named(String),
237 Given(Transition),
239}
240
241#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
243#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
244#[serde(untagged)]
245pub enum Inset {
246 Uniform(f32),
248 Sides([f32; 4]),
250}
251
252#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
254#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
255#[serde(rename_all = "snake_case")]
256pub enum Fit {
257 #[default]
259 Contain,
260 Cover,
262 Fill,
264 None,
266}
267
268#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
270#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
271#[serde(rename_all = "kebab-case")]
272pub enum Align {
273 #[default]
275 Center,
276 Top,
278 Bottom,
280 Left,
282 Right,
284 TopLeft,
286 TopRight,
288 BottomLeft,
290 BottomRight,
292}
293
294#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
296#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
297#[serde(rename_all = "snake_case")]
298pub enum FileKind {
299 Video,
301 Image,
303 Audio,
305}
306
307#[derive(Clone, Debug, PartialEq, Serialize)]
309#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
310#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
311pub enum Node {
312 Sequence(Sequence),
314 Stack(Stack),
316 Grid(Grid),
318 Media(Media),
320 Text(Text),
322 Color(ColorFill),
324 Captions(Captions),
326 Layout(Layout),
329 Audio(Audio),
331 Raw(Raw),
334 Slideshow(Slideshow),
336 CaptionedVideo(CaptionedVideo),
338 Title(Title),
341 TitleCard(Title),
343 LowerThird(LowerThird),
345 EndCard(EndCard),
348 ComparisonGrid(ComparisonGrid),
350 Reframe(Reframe),
353 Logo(Logo),
355 MusicBed(MusicBed),
357}
358
359#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
361#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
362#[serde(deny_unknown_fields)]
363pub struct Sequence {
364 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub id: Option<String>,
367 pub children: Vec<Node>,
369 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub transition: Option<TransitionChoice>,
374 #[serde(default, skip_serializing_if = "Option::is_none")]
377 pub at: Option<f64>,
378 #[serde(default, skip_serializing_if = "Option::is_none")]
381 pub duration: Option<f64>,
382}
383
384#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
386#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
387#[serde(deny_unknown_fields)]
388pub struct Stack {
389 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub id: Option<String>,
392 pub children: Vec<Node>,
395 #[serde(default, skip_serializing_if = "Option::is_none")]
398 pub at: Option<f64>,
399 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub duration: Option<f64>,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
406 pub inset: Option<Inset>,
407}
408
409#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
411#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
412#[serde(deny_unknown_fields)]
413pub struct Grid {
414 #[serde(default, skip_serializing_if = "Option::is_none")]
416 pub id: Option<String>,
417 pub children: Vec<Node>,
419 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub columns: Option<u32>,
422 #[serde(default)]
424 pub gap: f32,
425 #[serde(default)]
428 pub length: GridLength,
429 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub at: Option<f64>,
433 #[serde(default, skip_serializing_if = "Option::is_none")]
436 pub duration: Option<f64>,
437 #[serde(default, skip_serializing_if = "Option::is_none")]
440 pub inset: Option<Inset>,
441}
442
443#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
445#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
446#[serde(rename_all = "snake_case")]
447pub enum GridLength {
448 #[default]
450 Shortest,
451 LongestFreeze,
453 LongestLoop,
455}
456
457#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
459#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
460#[serde(deny_unknown_fields)]
461pub struct Media {
462 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub id: Option<String>,
465 pub path: String,
467 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub kind: Option<FileKind>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub trim_start: Option<f64>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub trim_end: Option<f64>,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub speed: Option<f64>,
479 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
481 pub r#loop: bool,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub volume: Option<f32>,
485 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
487 pub muted: bool,
488 #[serde(default, skip_serializing_if = "Option::is_none")]
490 pub opacity: Option<f32>,
491 #[serde(default, skip_serializing_if = "Option::is_none")]
493 pub fade_in: Option<f64>,
494 #[serde(default, skip_serializing_if = "Option::is_none")]
496 pub fade_out: Option<f64>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
500 pub zoom: Option<[f32; 2]>,
501 #[serde(default, skip_serializing_if = "Option::is_none")]
504 pub blur: Option<f32>,
505 #[serde(default, skip_serializing_if = "Option::is_none")]
507 pub label: Option<String>,
508 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub at: Option<f64>,
512 #[serde(default, skip_serializing_if = "Option::is_none")]
515 pub duration: Option<f64>,
516 #[serde(default, skip_serializing_if = "Option::is_none")]
519 pub inset: Option<Inset>,
520 #[serde(default, skip_serializing_if = "Option::is_none")]
522 pub fit: Option<Fit>,
523 #[serde(default, skip_serializing_if = "Option::is_none")]
525 pub align: Option<Align>,
526}
527
528impl Media {
529 #[must_use]
531 pub fn new(path: impl Into<String>) -> Self {
532 Self {
533 id: None,
534 path: path.into(),
535 kind: None,
536 trim_start: None,
537 trim_end: None,
538 speed: None,
539 r#loop: false,
540 volume: None,
541 muted: false,
542 opacity: None,
543 fade_in: None,
544 fade_out: None,
545 zoom: None,
546 blur: None,
547 label: None,
548 at: None,
549 duration: None,
550 inset: None,
551 fit: None,
552 align: None,
553 }
554 }
555}
556
557#[derive(Clone, Debug, PartialEq, Serialize)]
559#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
560#[serde(untagged)]
561pub enum MediaItem {
562 Path(String),
564 Node(Box<Node>),
566}
567
568#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
570#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
571#[serde(deny_unknown_fields)]
572pub struct Text {
573 #[serde(default, skip_serializing_if = "Option::is_none")]
575 pub id: Option<String>,
576 pub text: String,
578 #[serde(default)]
580 pub style: TextRole,
581 #[serde(default, skip_serializing_if = "Option::is_none")]
583 pub font_size: Option<f32>,
584 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub font_family: Option<String>,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub color: Option<Rgba>,
590 #[serde(default, skip_serializing_if = "Option::is_none")]
592 pub background: Option<Rgba>,
593 #[serde(default, skip_serializing_if = "Option::is_none")]
595 pub outline: Option<Rgba>,
596 #[serde(default, skip_serializing_if = "Option::is_none")]
599 pub max_lines: Option<u32>,
600 #[serde(default, skip_serializing_if = "Option::is_none")]
602 pub opacity: Option<f32>,
603 #[serde(default, skip_serializing_if = "Option::is_none")]
605 pub fade: Option<f64>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub at: Option<f64>,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub duration: Option<f64>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub inset: Option<Inset>,
618 #[serde(default, skip_serializing_if = "Option::is_none")]
620 pub align: Option<Align>,
621}
622
623#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
625#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
626#[serde(rename_all = "snake_case")]
627pub enum TextRole {
628 Title,
630 Subtitle,
632 #[default]
634 Body,
635 Label,
637}
638
639#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
641#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
642#[serde(deny_unknown_fields)]
643pub struct ColorFill {
644 #[serde(default, skip_serializing_if = "Option::is_none")]
646 pub id: Option<String>,
647 #[serde(default, skip_serializing_if = "Option::is_none")]
649 pub color: Option<Rgba>,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
653 pub at: Option<f64>,
654 #[serde(default, skip_serializing_if = "Option::is_none")]
657 pub duration: Option<f64>,
658 #[serde(default, skip_serializing_if = "Option::is_none")]
661 pub inset: Option<Inset>,
662}
663
664#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
666#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
667#[cfg_attr(feature = "schema", schemars(rename = "CaptionsNode"))]
668#[serde(deny_unknown_fields)]
669pub struct Captions {
670 #[serde(default, skip_serializing_if = "Option::is_none")]
672 pub id: Option<String>,
673 #[serde(default, skip_serializing_if = "Vec::is_empty")]
675 pub cues: Vec<Cue>,
676 #[serde(default, skip_serializing_if = "Vec::is_empty")]
679 pub words: Vec<Cue>,
680 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub file: Option<String>,
683 #[serde(default, skip_serializing_if = "Option::is_none")]
685 pub srt: Option<String>,
686 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub vtt: Option<String>,
689 #[serde(default)]
691 pub style: CaptionStyle,
692 #[serde(default, skip_serializing_if = "Option::is_none")]
695 pub max_chars: Option<u32>,
696 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub font_size: Option<f32>,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
701 pub color: Option<Rgba>,
702 #[serde(default, skip_serializing_if = "Option::is_none")]
705 pub position: Option<f32>,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub at: Option<f64>,
710 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub duration: Option<f64>,
714}
715
716#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
718#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
719#[serde(deny_unknown_fields)]
720pub struct Cue {
721 pub start: f64,
723 pub end: f64,
725 pub text: String,
727}
728
729#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
731#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
732#[serde(rename_all = "kebab-case")]
733pub enum CaptionStyle {
734 #[default]
736 Outline,
737 Boxed,
739 BarBottom,
742 BoldCenter,
745}
746
747#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
749#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
750#[serde(deny_unknown_fields)]
751pub struct Layout {
752 #[serde(default, skip_serializing_if = "Option::is_none")]
754 pub id: Option<String>,
755 pub root: Box<LayoutNode>,
757 #[serde(default, skip_serializing_if = "Option::is_none")]
760 pub at: Option<f64>,
761 #[serde(default, skip_serializing_if = "Option::is_none")]
764 pub duration: Option<f64>,
765 #[serde(default, skip_serializing_if = "Option::is_none")]
768 pub inset: Option<Inset>,
769 #[serde(default, skip_serializing_if = "Option::is_none")]
771 pub align: Option<Align>,
772}
773
774#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
776#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
777#[serde(deny_unknown_fields)]
778pub struct Audio {
779 #[serde(default, skip_serializing_if = "Option::is_none")]
781 pub id: Option<String>,
782 pub path: String,
784 #[serde(default, skip_serializing_if = "Option::is_none")]
786 pub trim_start: Option<f64>,
787 #[serde(default, skip_serializing_if = "Option::is_none")]
789 pub trim_end: Option<f64>,
790 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
792 pub r#loop: bool,
793 #[serde(default, skip_serializing_if = "Option::is_none")]
795 pub volume: Option<f32>,
796 #[serde(default, skip_serializing_if = "Option::is_none")]
798 pub fade_in: Option<f64>,
799 #[serde(default, skip_serializing_if = "Option::is_none")]
801 pub fade_out: Option<f64>,
802 #[serde(default, skip_serializing_if = "Option::is_none")]
804 pub duck_under: Option<String>,
805 #[serde(default, skip_serializing_if = "Option::is_none")]
807 pub duck_depth_db: Option<f32>,
808 #[serde(default, skip_serializing_if = "Option::is_none")]
811 pub at: Option<f64>,
812 #[serde(default, skip_serializing_if = "Option::is_none")]
815 pub duration: Option<f64>,
816}
817
818#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
820#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
821#[serde(deny_unknown_fields)]
822pub struct Raw {
823 #[serde(default, skip_serializing_if = "Vec::is_empty")]
826 pub sources: Vec<MediaSource>,
827 #[serde(default, skip_serializing_if = "Vec::is_empty")]
829 pub tracks: Vec<Track>,
830 #[serde(default, skip_serializing_if = "Vec::is_empty")]
832 pub compositions: Vec<Composition>,
833 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
836 pub assets: BTreeMap<String, String>,
837}
838
839#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
841#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
842#[serde(deny_unknown_fields)]
843pub struct Slideshow {
844 #[serde(default, skip_serializing_if = "Option::is_none")]
846 pub id: Option<String>,
847 pub items: Vec<MediaItem>,
849 #[serde(default, skip_serializing_if = "Option::is_none")]
852 pub seconds: Option<f64>,
853 #[serde(default, skip_serializing_if = "Option::is_none")]
856 pub fit_to: Option<String>,
857 #[serde(default, skip_serializing_if = "Option::is_none")]
859 pub transition: Option<TransitionChoice>,
860 #[serde(default, skip_serializing_if = "Option::is_none")]
863 pub ken_burns: Option<f32>,
864 #[serde(default, skip_serializing_if = "Option::is_none")]
866 pub fit: Option<Fit>,
867 #[serde(default, skip_serializing_if = "Option::is_none")]
869 pub at: Option<f64>,
870}
871
872#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
874#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
875#[serde(deny_unknown_fields)]
876pub struct CaptionedVideo {
877 #[serde(default, skip_serializing_if = "Option::is_none")]
880 pub id: Option<String>,
881 pub video: MediaItem,
883 pub captions: Box<Captions>,
885 #[serde(default, skip_serializing_if = "Option::is_none")]
887 pub fit: Option<Fit>,
888 #[serde(default, skip_serializing_if = "Option::is_none")]
890 pub at: Option<f64>,
891}
892
893#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
895#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
896#[serde(deny_unknown_fields)]
897pub struct Title {
898 #[serde(default, skip_serializing_if = "Option::is_none")]
900 pub id: Option<String>,
901 pub text: String,
903 #[serde(default, skip_serializing_if = "Option::is_none")]
905 pub subtitle: Option<String>,
906 #[serde(default, skip_serializing_if = "Option::is_none")]
908 pub align: Option<Align>,
909 #[serde(default, skip_serializing_if = "Option::is_none")]
911 pub fade: Option<f64>,
912 #[serde(default, skip_serializing_if = "Option::is_none")]
914 pub background: Option<Rgba>,
915 #[serde(default, skip_serializing_if = "Option::is_none")]
918 pub at: Option<f64>,
919 #[serde(default, skip_serializing_if = "Option::is_none")]
922 pub duration: Option<f64>,
923}
924
925#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
927#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
928#[serde(deny_unknown_fields)]
929pub struct LowerThird {
930 #[serde(default, skip_serializing_if = "Option::is_none")]
932 pub id: Option<String>,
933 pub text: String,
935 #[serde(default, skip_serializing_if = "Option::is_none")]
937 pub subtitle: Option<String>,
938 #[serde(default, skip_serializing_if = "Option::is_none")]
940 pub fade: Option<f64>,
941 #[serde(default, skip_serializing_if = "Option::is_none")]
944 pub at: Option<f64>,
945 #[serde(default, skip_serializing_if = "Option::is_none")]
948 pub duration: Option<f64>,
949}
950
951#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
953#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
954#[serde(deny_unknown_fields)]
955pub struct EndCard {
956 #[serde(default, skip_serializing_if = "Option::is_none")]
958 pub id: Option<String>,
959 pub text: String,
961 #[serde(default, skip_serializing_if = "Option::is_none")]
963 pub subtitle: Option<String>,
964 #[serde(default, skip_serializing_if = "Option::is_none")]
966 pub logo: Option<String>,
967 #[serde(default, skip_serializing_if = "Option::is_none")]
969 pub background: Option<Rgba>,
970 #[serde(default, skip_serializing_if = "Option::is_none")]
972 pub fade: Option<f64>,
973 #[serde(default, skip_serializing_if = "Option::is_none")]
976 pub at: Option<f64>,
977 #[serde(default, skip_serializing_if = "Option::is_none")]
980 pub duration: Option<f64>,
981}
982
983#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
985#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
986#[serde(deny_unknown_fields)]
987pub struct ComparisonGrid {
988 #[serde(default, skip_serializing_if = "Option::is_none")]
990 pub id: Option<String>,
991 pub items: Vec<MediaItem>,
993 #[serde(default, skip_serializing_if = "Option::is_none")]
995 pub columns: Option<u32>,
996 #[serde(default)]
998 pub gap: f32,
999 #[serde(default)]
1001 pub length: GridLength,
1002 #[serde(default, skip_serializing_if = "Option::is_none")]
1004 pub audio_from: Option<u32>,
1005 #[serde(default, skip_serializing_if = "Option::is_none")]
1008 pub at: Option<f64>,
1009 #[serde(default, skip_serializing_if = "Option::is_none")]
1012 pub duration: Option<f64>,
1013}
1014
1015#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1017#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1018#[serde(deny_unknown_fields)]
1019pub struct Reframe {
1020 #[serde(default, skip_serializing_if = "Option::is_none")]
1023 pub id: Option<String>,
1024 pub video: MediaItem,
1026 #[serde(default, skip_serializing_if = "Option::is_none")]
1029 pub blur: Option<f32>,
1030 #[serde(default, skip_serializing_if = "Option::is_none")]
1032 pub at: Option<f64>,
1033}
1034
1035#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1037#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1038#[serde(deny_unknown_fields)]
1039pub struct Logo {
1040 #[serde(default, skip_serializing_if = "Option::is_none")]
1042 pub id: Option<String>,
1043 pub path: String,
1045 #[serde(default, skip_serializing_if = "Option::is_none")]
1047 pub corner: Option<Align>,
1048 #[serde(default, skip_serializing_if = "Option::is_none")]
1051 pub size: Option<f32>,
1052 #[serde(default, skip_serializing_if = "Option::is_none")]
1054 pub margin: Option<f32>,
1055 #[serde(default, skip_serializing_if = "Option::is_none")]
1057 pub opacity: Option<f32>,
1058 #[serde(default, skip_serializing_if = "Option::is_none")]
1061 pub at: Option<f64>,
1062 #[serde(default, skip_serializing_if = "Option::is_none")]
1065 pub duration: Option<f64>,
1066}
1067
1068#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1070#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1071#[serde(deny_unknown_fields)]
1072pub struct MusicBed {
1073 #[serde(default, skip_serializing_if = "Option::is_none")]
1075 pub id: Option<String>,
1076 pub path: String,
1078 #[serde(default, skip_serializing_if = "Option::is_none")]
1080 pub volume: Option<f32>,
1081 #[serde(default, skip_serializing_if = "Option::is_none")]
1083 pub fade_in: Option<f64>,
1084 #[serde(default, skip_serializing_if = "Option::is_none")]
1086 pub fade_out: Option<f64>,
1087 #[serde(default, skip_serializing_if = "Option::is_none")]
1089 pub duck_under: Option<String>,
1090 #[serde(default, skip_serializing_if = "Option::is_none")]
1092 pub duck_depth_db: Option<f32>,
1093 #[serde(default = "yes")]
1096 pub r#loop: bool,
1097 #[serde(default, skip_serializing_if = "Option::is_none")]
1100 pub at: Option<f64>,
1101 #[serde(default, skip_serializing_if = "Option::is_none")]
1104 pub duration: Option<f64>,
1105}
1106
1107const fn yes() -> bool {
1108 true
1109}