1use std::collections::BTreeMap;
10
11use alpha2_model::{
12 Animated, Composition, Easing, EffectValue, LayoutNode, MediaSource, Rgba, TextShadow,
13 TextStroke, 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")]
47 pub audio: Vec<Node>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub duration: Option<f64>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub loudness: Option<Loudness>,
55}
56
57const fn compose_version() -> u32 {
58 COMPOSE_VERSION
59}
60
61#[derive(Clone, Debug, PartialEq, Serialize)]
63#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
64#[serde(untagged)]
65pub enum Canvas {
66 Preset(String),
69 Custom(CustomCanvas),
71}
72
73impl Default for Canvas {
74 fn default() -> Self {
75 Self::Preset("youtube-1080p".to_owned())
76 }
77}
78
79#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
82#[serde(deny_unknown_fields)]
83pub struct CustomCanvas {
84 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub preset: Option<String>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub width: Option<u32>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub height: Option<u32>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub frame_rate: Option<FrameRateChoice>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub background: Option<Rgba>,
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
104#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
105#[serde(untagged)]
106pub enum FrameRateChoice {
107 Number(f64),
109 Exact(alpha2_time::FrameRate),
111}
112
113#[derive(Clone, Debug, PartialEq, Serialize)]
115#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
116#[serde(untagged)]
117pub enum ThemeChoice {
118 Named(String),
120 Custom(Box<Theme>),
122}
123
124impl Default for ThemeChoice {
125 fn default() -> Self {
126 Self::Named("clean-dark".to_owned())
127 }
128}
129
130#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
133#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
134#[serde(deny_unknown_fields)]
135pub struct Theme {
136 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub base: Option<String>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub font_family: Option<String>,
143 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub text: Option<Rgba>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub background: Option<Rgba>,
149 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub surface: Option<Rgba>,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub panel: Option<Rgba>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub accent: Option<Rgba>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub title_size: Option<f32>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub subtitle_size: Option<f32>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub body_size: Option<f32>,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub caption_size: Option<f32>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub label_size: Option<f32>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub margin: Option<f32>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub transition: Option<Transition>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub still_seconds: Option<f64>,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub text_seconds: Option<f64>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub fade_seconds: Option<f64>,
188}
189
190#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
192#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
193#[serde(untagged)]
194pub enum Loudness {
195 Lufs(f64),
197 Target(crate::LoudnessTarget),
199}
200
201#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
204#[serde(deny_unknown_fields)]
205pub struct Transition {
206 #[serde(rename = "type")]
209 pub kind: String,
210 pub duration: f64,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub easing: Option<Easing>,
215 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
217 pub params: BTreeMap<String, Animated<EffectValue>>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub version: Option<u32>,
221}
222
223#[derive(Clone, Debug, PartialEq, Serialize)]
225#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
226#[serde(untagged)]
227pub enum TransitionChoice {
228 Named(String),
231 Given(Transition),
233}
234
235#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
237#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
238#[serde(untagged)]
239pub enum Inset {
240 Uniform(f32),
242 Sides([f32; 4]),
244}
245
246#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
248#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
249#[serde(rename_all = "snake_case")]
250pub enum Fit {
251 #[default]
253 Contain,
254 Cover,
256 Fill,
258 None,
260}
261
262#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
264#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
265#[serde(rename_all = "kebab-case")]
266pub enum Align {
267 #[default]
269 Center,
270 Top,
272 Bottom,
274 Left,
276 Right,
278 TopLeft,
280 TopRight,
282 BottomLeft,
284 BottomRight,
286}
287
288#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
290#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
291#[serde(rename_all = "snake_case")]
292pub enum FileKind {
293 Video,
295 Image,
297 Audio,
299}
300
301#[derive(Clone, Debug, PartialEq, Serialize)]
303#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
304#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
305pub enum Node {
306 Sequence(Sequence),
308 Stack(Stack),
310 Grid(Grid),
312 Media(Media),
314 Text(Text),
316 Color(ColorFill),
318 Captions(Captions),
320 Layout(Layout),
322 Audio(Audio),
324 Raw(Raw),
326 Slideshow(Slideshow),
328 CaptionedVideo(CaptionedVideo),
330 Title(Title),
332 TitleCard(Title),
334 LowerThird(LowerThird),
336 EndCard(EndCard),
339 ComparisonGrid(ComparisonGrid),
341 Reframe(Reframe),
343 Logo(Logo),
345 MusicBed(MusicBed),
347 Counter(Counter),
349}
350
351#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
353#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
354#[serde(deny_unknown_fields)]
355pub struct Sequence {
356 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub id: Option<String>,
359 pub children: Vec<Node>,
361 #[serde(default, skip_serializing_if = "Option::is_none")]
365 pub transition: Option<TransitionChoice>,
366 #[serde(default, skip_serializing_if = "Option::is_none")]
368 pub at: Option<f64>,
369 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub duration: Option<f64>,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
374 pub enter: Option<Motion>,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
377 pub exit: Option<Motion>,
378}
379
380#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
382#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
383#[serde(deny_unknown_fields)]
384pub struct Stack {
385 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub id: Option<String>,
388 pub children: Vec<Node>,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub at: Option<f64>,
393 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub duration: Option<f64>,
396 #[serde(default, skip_serializing_if = "Option::is_none")]
398 pub inset: Option<Inset>,
399 #[serde(default, skip_serializing_if = "Option::is_none")]
401 pub enter: Option<Motion>,
402 #[serde(default, skip_serializing_if = "Option::is_none")]
404 pub exit: Option<Motion>,
405 #[serde(default, skip_serializing_if = "Option::is_none")]
409 pub stagger: Option<f64>,
410}
411
412#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
414#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
415#[serde(deny_unknown_fields)]
416pub struct Grid {
417 #[serde(default, skip_serializing_if = "Option::is_none")]
419 pub id: Option<String>,
420 pub children: Vec<Node>,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub columns: Option<u32>,
425 #[serde(default)]
427 pub gap: f32,
428 #[serde(default)]
431 pub length: GridLength,
432 #[serde(default, skip_serializing_if = "Option::is_none")]
434 pub at: Option<f64>,
435 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub duration: Option<f64>,
438 #[serde(default, skip_serializing_if = "Option::is_none")]
440 pub inset: Option<Inset>,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
443 pub enter: Option<Motion>,
444 #[serde(default, skip_serializing_if = "Option::is_none")]
446 pub exit: Option<Motion>,
447 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub stagger: Option<f64>,
452}
453
454#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
456#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
457#[serde(rename_all = "snake_case")]
458pub enum GridLength {
459 #[default]
461 Shortest,
462 LongestFreeze,
464 LongestLoop,
466}
467
468#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
470#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
471#[serde(deny_unknown_fields)]
472pub struct Media {
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub id: Option<String>,
476 pub path: String,
478 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub kind: Option<FileKind>,
481 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub trim_start: Option<f64>,
484 #[serde(default, skip_serializing_if = "Option::is_none")]
486 pub trim_end: Option<f64>,
487 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub speed: Option<f64>,
490 #[serde(default, skip_serializing_if = "Option::is_none")]
493 pub preserve_pitch: Option<bool>,
494 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
496 pub r#loop: bool,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub volume: Option<f32>,
500 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
502 pub muted: bool,
503 #[serde(default, skip_serializing_if = "Option::is_none")]
505 pub opacity: Option<f32>,
506 #[serde(default, skip_serializing_if = "Option::is_none")]
508 pub fade_in: Option<f64>,
509 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub fade_out: Option<f64>,
512 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub zoom: Option<[f32; 2]>,
515 #[serde(default, skip_serializing_if = "Option::is_none")]
517 pub blur: Option<f32>,
518 #[serde(default, skip_serializing_if = "Option::is_none")]
520 pub label: Option<String>,
521 #[serde(default, skip_serializing_if = "Option::is_none")]
523 pub at: Option<f64>,
524 #[serde(default, skip_serializing_if = "Option::is_none")]
526 pub duration: Option<f64>,
527 #[serde(default, skip_serializing_if = "Option::is_none")]
529 pub inset: Option<Inset>,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
532 pub fit: Option<Fit>,
533 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub align: Option<Align>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
538 pub enter: Option<Motion>,
539 #[serde(default, skip_serializing_if = "Option::is_none")]
541 pub exit: Option<Motion>,
542}
543
544impl Media {
545 #[must_use]
547 pub fn new(path: impl Into<String>) -> Self {
548 Self {
549 id: None,
550 path: path.into(),
551 kind: None,
552 trim_start: None,
553 trim_end: None,
554 speed: None,
555 preserve_pitch: None,
556 r#loop: false,
557 volume: None,
558 muted: false,
559 opacity: None,
560 fade_in: None,
561 fade_out: None,
562 zoom: None,
563 blur: None,
564 label: None,
565 at: None,
566 duration: None,
567 inset: None,
568 fit: None,
569 align: None,
570 enter: None,
571 exit: None,
572 }
573 }
574}
575
576#[derive(Clone, Debug, PartialEq, Serialize)]
578#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
579#[serde(untagged)]
580pub enum MediaItem {
581 Path(String),
583 Node(Box<Node>),
585}
586
587#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
589#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
590#[serde(deny_unknown_fields)]
591pub struct Text {
592 #[serde(default, skip_serializing_if = "Option::is_none")]
594 pub id: Option<String>,
595 pub text: String,
597 #[serde(default)]
599 pub style: TextRole,
600 #[serde(default, skip_serializing_if = "Option::is_none")]
602 pub font_size: Option<f32>,
603 #[serde(default, skip_serializing_if = "Option::is_none")]
605 pub font_family: Option<String>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
608 pub color: Option<Rgba>,
609 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub background: Option<Rgba>,
612 #[serde(default, skip_serializing_if = "Vec::is_empty")]
615 pub strokes: Vec<TextStroke>,
616 #[serde(default, skip_serializing_if = "Vec::is_empty")]
619 pub shadows: Vec<TextShadow>,
620 #[serde(default, skip_serializing_if = "Option::is_none")]
623 pub max_lines: Option<u32>,
624 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub opacity: Option<f32>,
627 #[serde(default, skip_serializing_if = "Option::is_none")]
629 pub fade: Option<f64>,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub at: Option<f64>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub duration: Option<f64>,
636 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub inset: Option<Inset>,
639 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub align: Option<Align>,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
644 pub enter: Option<Motion>,
645 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub exit: Option<Motion>,
648 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub reveal: Option<Reveal>,
651}
652
653#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
655#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
656#[serde(rename_all = "snake_case")]
657pub enum TextRole {
658 Title,
660 Subtitle,
662 #[default]
664 Body,
665 Label,
667}
668
669#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
671#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
672#[serde(deny_unknown_fields)]
673pub struct ColorFill {
674 #[serde(default, skip_serializing_if = "Option::is_none")]
676 pub id: Option<String>,
677 #[serde(default, skip_serializing_if = "Option::is_none")]
679 pub color: Option<Rgba>,
680 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub at: Option<f64>,
683 #[serde(default, skip_serializing_if = "Option::is_none")]
685 pub duration: Option<f64>,
686 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub inset: Option<Inset>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
691 pub enter: Option<Motion>,
692 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub exit: Option<Motion>,
695}
696
697#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
699#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
700#[cfg_attr(feature = "schema", schemars(rename = "CaptionsNode"))]
701#[serde(deny_unknown_fields)]
702pub struct Captions {
703 #[serde(default, skip_serializing_if = "Option::is_none")]
705 pub id: Option<String>,
706 #[serde(default, skip_serializing_if = "Vec::is_empty")]
708 pub cues: Vec<Cue>,
709 #[serde(default, skip_serializing_if = "Vec::is_empty")]
712 pub words: Vec<Cue>,
713 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub file: Option<String>,
716 #[serde(default, skip_serializing_if = "Option::is_none")]
718 pub srt: Option<String>,
719 #[serde(default, skip_serializing_if = "Option::is_none")]
721 pub vtt: Option<String>,
722 #[serde(default)]
724 pub style: CaptionStyle,
725 #[serde(default, skip_serializing_if = "Option::is_none")]
728 pub max_chars: Option<u32>,
729 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub font_size: Option<f32>,
732 #[serde(default, skip_serializing_if = "Option::is_none")]
734 pub color: Option<Rgba>,
735 #[serde(default, skip_serializing_if = "Option::is_none")]
737 pub highlight: Option<Rgba>,
738 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
741 pub speakers: BTreeMap<String, SpeakerLook>,
742 #[serde(default, skip_serializing_if = "Option::is_none")]
745 pub position: Option<f32>,
746 #[serde(default, skip_serializing_if = "Option::is_none")]
748 pub at: Option<f64>,
749 #[serde(default, skip_serializing_if = "Option::is_none")]
751 pub duration: Option<f64>,
752}
753
754#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
756#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
757#[serde(deny_unknown_fields)]
758pub struct Cue {
759 pub start: f64,
761 pub end: f64,
763 pub text: String,
765 #[serde(default, skip_serializing_if = "Option::is_none")]
767 pub speaker: Option<String>,
768}
769
770#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
772#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
773#[serde(deny_unknown_fields)]
774pub struct SpeakerLook {
775 #[serde(default, skip_serializing_if = "Option::is_none")]
777 pub color: Option<Rgba>,
778 #[serde(default, skip_serializing_if = "Option::is_none")]
780 pub position: Option<alpha2_model::Vec2>,
781}
782
783#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
785#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
786#[serde(rename_all = "kebab-case")]
787pub enum CaptionStyle {
788 #[default]
790 Outline,
791 Boxed,
793 BarBottom,
796 BoldCenter,
798 Bold,
800 Karaoke,
803 Pop,
805}
806
807#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
809#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
810#[serde(deny_unknown_fields)]
811pub struct Layout {
812 #[serde(default, skip_serializing_if = "Option::is_none")]
814 pub id: Option<String>,
815 pub root: Box<LayoutNode>,
817 #[serde(default, skip_serializing_if = "Option::is_none")]
819 pub at: Option<f64>,
820 #[serde(default, skip_serializing_if = "Option::is_none")]
822 pub duration: Option<f64>,
823 #[serde(default, skip_serializing_if = "Option::is_none")]
825 pub inset: Option<Inset>,
826 #[serde(default, skip_serializing_if = "Option::is_none")]
828 pub align: Option<Align>,
829 #[serde(default, skip_serializing_if = "Option::is_none")]
831 pub enter: Option<Motion>,
832 #[serde(default, skip_serializing_if = "Option::is_none")]
834 pub exit: Option<Motion>,
835}
836
837#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
839#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
840#[serde(deny_unknown_fields)]
841pub struct Audio {
842 #[serde(default, skip_serializing_if = "Option::is_none")]
844 pub id: Option<String>,
845 pub path: String,
847 #[serde(default, skip_serializing_if = "Option::is_none")]
849 pub trim_start: Option<f64>,
850 #[serde(default, skip_serializing_if = "Option::is_none")]
852 pub trim_end: Option<f64>,
853 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
855 pub r#loop: bool,
856 #[serde(default, skip_serializing_if = "Option::is_none")]
858 pub volume: Option<f32>,
859 #[serde(default, skip_serializing_if = "Option::is_none")]
861 pub fade_in: Option<f64>,
862 #[serde(default, skip_serializing_if = "Option::is_none")]
864 pub fade_out: Option<f64>,
865 #[serde(default, skip_serializing_if = "Option::is_none")]
867 pub duck_under: Option<String>,
868 #[serde(default, skip_serializing_if = "Option::is_none")]
870 pub duck_depth_db: Option<f32>,
871 #[serde(default, skip_serializing_if = "Option::is_none")]
873 pub at: Option<f64>,
874 #[serde(default, skip_serializing_if = "Option::is_none")]
876 pub duration: Option<f64>,
877}
878
879#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
881#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
882#[serde(deny_unknown_fields)]
883pub struct Raw {
884 #[serde(default, skip_serializing_if = "Vec::is_empty")]
887 pub sources: Vec<MediaSource>,
888 #[serde(default, skip_serializing_if = "Vec::is_empty")]
890 pub tracks: Vec<Track>,
891 #[serde(default, skip_serializing_if = "Vec::is_empty")]
893 pub compositions: Vec<Composition>,
894 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
896 pub assets: BTreeMap<String, String>,
897}
898
899#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
901#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
902#[serde(deny_unknown_fields)]
903pub struct Slideshow {
904 #[serde(default, skip_serializing_if = "Option::is_none")]
906 pub id: Option<String>,
907 pub items: Vec<MediaItem>,
909 #[serde(default, skip_serializing_if = "Option::is_none")]
912 pub seconds: Option<f64>,
913 #[serde(default, skip_serializing_if = "Option::is_none")]
915 pub fit_to: Option<String>,
916 #[serde(default, skip_serializing_if = "Option::is_none")]
918 pub transition: Option<TransitionChoice>,
919 #[serde(default, skip_serializing_if = "Option::is_none")]
922 pub ken_burns: Option<f32>,
923 #[serde(default, skip_serializing_if = "Option::is_none")]
925 pub fit: Option<Fit>,
926 #[serde(default, skip_serializing_if = "Option::is_none")]
928 pub at: Option<f64>,
929}
930
931#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
933#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
934#[serde(deny_unknown_fields)]
935pub struct CaptionedVideo {
936 #[serde(default, skip_serializing_if = "Option::is_none")]
938 pub id: Option<String>,
939 pub video: MediaItem,
941 pub captions: Box<Captions>,
943 #[serde(default, skip_serializing_if = "Option::is_none")]
945 pub fit: Option<Fit>,
946 #[serde(default, skip_serializing_if = "Option::is_none")]
948 pub at: 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 Title {
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 align: Option<Align>,
967 #[serde(default, skip_serializing_if = "Option::is_none")]
969 pub fade: Option<f64>,
970 #[serde(default, skip_serializing_if = "Option::is_none")]
972 pub background: Option<Rgba>,
973 #[serde(default, skip_serializing_if = "Option::is_none")]
975 pub at: Option<f64>,
976 #[serde(default, skip_serializing_if = "Option::is_none")]
978 pub duration: Option<f64>,
979 #[serde(default, skip_serializing_if = "Option::is_none")]
981 pub enter: Option<Motion>,
982 #[serde(default, skip_serializing_if = "Option::is_none")]
984 pub exit: Option<Motion>,
985}
986
987#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
989#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
990#[serde(deny_unknown_fields)]
991pub struct LowerThird {
992 #[serde(default, skip_serializing_if = "Option::is_none")]
994 pub id: Option<String>,
995 pub text: String,
997 #[serde(default, skip_serializing_if = "Option::is_none")]
999 pub subtitle: Option<String>,
1000 #[serde(default, skip_serializing_if = "Option::is_none")]
1002 pub fade: Option<f64>,
1003 #[serde(default, skip_serializing_if = "Option::is_none")]
1005 pub at: Option<f64>,
1006 #[serde(default, skip_serializing_if = "Option::is_none")]
1008 pub duration: Option<f64>,
1009 #[serde(default, skip_serializing_if = "Option::is_none")]
1011 pub enter: Option<Motion>,
1012 #[serde(default, skip_serializing_if = "Option::is_none")]
1014 pub exit: Option<Motion>,
1015}
1016
1017#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1019#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1020#[serde(deny_unknown_fields)]
1021pub struct EndCard {
1022 #[serde(default, skip_serializing_if = "Option::is_none")]
1024 pub id: Option<String>,
1025 pub text: String,
1027 #[serde(default, skip_serializing_if = "Option::is_none")]
1029 pub subtitle: Option<String>,
1030 #[serde(default, skip_serializing_if = "Option::is_none")]
1032 pub logo: Option<String>,
1033 #[serde(default, skip_serializing_if = "Option::is_none")]
1035 pub background: Option<Rgba>,
1036 #[serde(default, skip_serializing_if = "Option::is_none")]
1038 pub fade: Option<f64>,
1039 #[serde(default, skip_serializing_if = "Option::is_none")]
1041 pub at: Option<f64>,
1042 #[serde(default, skip_serializing_if = "Option::is_none")]
1044 pub duration: Option<f64>,
1045 #[serde(default, skip_serializing_if = "Option::is_none")]
1047 pub enter: Option<Motion>,
1048 #[serde(default, skip_serializing_if = "Option::is_none")]
1050 pub exit: Option<Motion>,
1051}
1052
1053#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1055#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1056#[serde(deny_unknown_fields)]
1057pub struct ComparisonGrid {
1058 #[serde(default, skip_serializing_if = "Option::is_none")]
1060 pub id: Option<String>,
1061 pub items: Vec<MediaItem>,
1063 #[serde(default, skip_serializing_if = "Option::is_none")]
1065 pub columns: Option<u32>,
1066 #[serde(default)]
1068 pub gap: f32,
1069 #[serde(default)]
1071 pub length: GridLength,
1072 #[serde(default, skip_serializing_if = "Option::is_none")]
1074 pub audio_from: Option<u32>,
1075 #[serde(default, skip_serializing_if = "Option::is_none")]
1077 pub at: Option<f64>,
1078 #[serde(default, skip_serializing_if = "Option::is_none")]
1080 pub duration: Option<f64>,
1081}
1082
1083#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1085#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1086#[serde(deny_unknown_fields)]
1087pub struct Reframe {
1088 #[serde(default, skip_serializing_if = "Option::is_none")]
1090 pub id: Option<String>,
1091 pub video: MediaItem,
1093 #[serde(default, skip_serializing_if = "Option::is_none")]
1095 pub blur: Option<f32>,
1096 #[serde(default, skip_serializing_if = "Option::is_none")]
1098 pub at: Option<f64>,
1099}
1100
1101#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1103#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1104#[serde(deny_unknown_fields)]
1105pub struct Logo {
1106 #[serde(default, skip_serializing_if = "Option::is_none")]
1108 pub id: Option<String>,
1109 pub path: String,
1111 #[serde(default, skip_serializing_if = "Option::is_none")]
1113 pub corner: Option<Align>,
1114 #[serde(default, skip_serializing_if = "Option::is_none")]
1116 pub size: Option<f32>,
1117 #[serde(default, skip_serializing_if = "Option::is_none")]
1119 pub margin: Option<f32>,
1120 #[serde(default, skip_serializing_if = "Option::is_none")]
1122 pub opacity: Option<f32>,
1123 #[serde(default, skip_serializing_if = "Option::is_none")]
1125 pub at: Option<f64>,
1126 #[serde(default, skip_serializing_if = "Option::is_none")]
1128 pub duration: Option<f64>,
1129 #[serde(default, skip_serializing_if = "Option::is_none")]
1131 pub enter: Option<Motion>,
1132 #[serde(default, skip_serializing_if = "Option::is_none")]
1134 pub exit: Option<Motion>,
1135}
1136
1137#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1139#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1140#[serde(deny_unknown_fields)]
1141pub struct MusicBed {
1142 #[serde(default, skip_serializing_if = "Option::is_none")]
1144 pub id: Option<String>,
1145 pub path: String,
1147 #[serde(default, skip_serializing_if = "Option::is_none")]
1149 pub volume: Option<f32>,
1150 #[serde(default, skip_serializing_if = "Option::is_none")]
1152 pub fade_in: Option<f64>,
1153 #[serde(default, skip_serializing_if = "Option::is_none")]
1155 pub fade_out: Option<f64>,
1156 #[serde(default, skip_serializing_if = "Option::is_none")]
1158 pub duck_under: Option<String>,
1159 #[serde(default, skip_serializing_if = "Option::is_none")]
1161 pub duck_depth_db: Option<f32>,
1162 #[serde(default = "yes")]
1164 pub r#loop: bool,
1165 #[serde(default, skip_serializing_if = "Option::is_none")]
1167 pub at: Option<f64>,
1168 #[serde(default, skip_serializing_if = "Option::is_none")]
1170 pub duration: Option<f64>,
1171}
1172
1173const fn yes() -> bool {
1174 true
1175}
1176
1177#[derive(Clone, Debug, PartialEq, Serialize)]
1180#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1181#[serde(untagged)]
1182pub enum EasingChoice {
1183 Named(String),
1185 Curve(Easing),
1187}
1188
1189#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1191#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1192#[serde(deny_unknown_fields)]
1193pub struct Motion {
1194 #[serde(rename = "type")]
1198 pub kind: MotionKind,
1199 #[serde(default, skip_serializing_if = "Option::is_none")]
1201 pub duration: Option<f64>,
1202 #[serde(default, skip_serializing_if = "Option::is_none")]
1204 pub easing: Option<EasingChoice>,
1205 #[serde(default, skip_serializing_if = "Option::is_none")]
1207 pub direction: Option<Direction>,
1208 #[serde(default, skip_serializing_if = "Option::is_none")]
1210 pub distance: Option<f32>,
1211 #[serde(default, skip_serializing_if = "Option::is_none")]
1213 pub size: Option<f32>,
1214 #[serde(default, skip_serializing_if = "Option::is_none")]
1216 pub fade: Option<bool>,
1217}
1218
1219#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1221#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1222#[serde(rename_all = "snake_case")]
1223pub enum MotionKind {
1224 Fade,
1226 Rise,
1228 Slide,
1230 Scale,
1232}
1233
1234#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1236#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1237#[serde(rename_all = "snake_case")]
1238pub enum Direction {
1239 #[default]
1241 Up,
1242 Down,
1244 Left,
1246 Right,
1248}
1249
1250#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1253#[serde(deny_unknown_fields)]
1254pub struct Reveal {
1255 pub by: RevealUnit,
1257 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
1259 pub kind: Option<RevealKind>,
1260 #[serde(default, skip_serializing_if = "Option::is_none")]
1262 pub stagger: Option<f64>,
1263 #[serde(default, skip_serializing_if = "Option::is_none")]
1265 pub duration: Option<f64>,
1266 #[serde(default, skip_serializing_if = "Option::is_none")]
1268 pub distance: Option<f32>,
1269}
1270
1271#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1273#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1274#[serde(rename_all = "snake_case")]
1275pub enum RevealUnit {
1276 Word,
1278 Line,
1280}
1281
1282#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1284#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1285#[serde(rename_all = "snake_case")]
1286pub enum RevealKind {
1287 Fade,
1289 Rise,
1291}
1292
1293#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1295#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1296#[serde(deny_unknown_fields)]
1297pub struct Counter {
1298 #[serde(default, skip_serializing_if = "Option::is_none")]
1300 pub id: Option<String>,
1301 pub start: f64,
1303 pub end: f64,
1305 #[serde(default, skip_serializing_if = "Option::is_none")]
1308 pub count_duration: Option<f64>,
1309 #[serde(default, skip_serializing_if = "Option::is_none")]
1311 pub easing: Option<EasingChoice>,
1312 #[serde(default, skip_serializing_if = "Option::is_none")]
1314 pub format: Option<NumberFormat>,
1315 #[serde(default, skip_serializing_if = "Option::is_none")]
1317 pub style: Option<TextRole>,
1318 #[serde(default, skip_serializing_if = "Option::is_none")]
1320 pub font_size: Option<f32>,
1321 #[serde(default, skip_serializing_if = "Option::is_none")]
1323 pub font_family: Option<String>,
1324 #[serde(default, skip_serializing_if = "Option::is_none")]
1326 pub color: Option<Rgba>,
1327 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1329 pub strokes: Vec<TextStroke>,
1330 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1332 pub shadows: Vec<TextShadow>,
1333 #[serde(default, skip_serializing_if = "Option::is_none")]
1335 pub opacity: Option<f32>,
1336 #[serde(default, skip_serializing_if = "Option::is_none")]
1338 pub at: Option<f64>,
1339 #[serde(default, skip_serializing_if = "Option::is_none")]
1341 pub duration: Option<f64>,
1342 #[serde(default, skip_serializing_if = "Option::is_none")]
1344 pub inset: Option<Inset>,
1345 #[serde(default, skip_serializing_if = "Option::is_none")]
1347 pub align: Option<Align>,
1348 #[serde(default, skip_serializing_if = "Option::is_none")]
1350 pub enter: Option<Motion>,
1351 #[serde(default, skip_serializing_if = "Option::is_none")]
1353 pub exit: Option<Motion>,
1354}
1355
1356#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1358#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1359#[serde(deny_unknown_fields)]
1360pub struct NumberFormat {
1361 #[serde(default, skip_serializing_if = "Option::is_none")]
1363 pub decimals: Option<u32>,
1364 #[serde(default, skip_serializing_if = "Option::is_none")]
1366 pub thousands: Option<String>,
1367 #[serde(default, skip_serializing_if = "Option::is_none")]
1369 pub point: Option<String>,
1370 #[serde(default, skip_serializing_if = "Option::is_none")]
1372 pub prefix: Option<String>,
1373 #[serde(default, skip_serializing_if = "Option::is_none")]
1375 pub suffix: Option<String>,
1376}