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}
348
349#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
351#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
352#[serde(deny_unknown_fields)]
353pub struct Sequence {
354 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub id: Option<String>,
357 pub children: Vec<Node>,
359 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub transition: Option<TransitionChoice>,
364 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub at: Option<f64>,
367 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub duration: Option<f64>,
370}
371
372#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
374#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
375#[serde(deny_unknown_fields)]
376pub struct Stack {
377 #[serde(default, skip_serializing_if = "Option::is_none")]
379 pub id: Option<String>,
380 pub children: Vec<Node>,
382 #[serde(default, skip_serializing_if = "Option::is_none")]
384 pub at: Option<f64>,
385 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub duration: Option<f64>,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub inset: Option<Inset>,
391}
392
393#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
395#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
396#[serde(deny_unknown_fields)]
397pub struct Grid {
398 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub id: Option<String>,
401 pub children: Vec<Node>,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub columns: Option<u32>,
406 #[serde(default)]
408 pub gap: f32,
409 #[serde(default)]
412 pub length: GridLength,
413 #[serde(default, skip_serializing_if = "Option::is_none")]
415 pub at: Option<f64>,
416 #[serde(default, skip_serializing_if = "Option::is_none")]
418 pub duration: Option<f64>,
419 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub inset: Option<Inset>,
422}
423
424#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
426#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
427#[serde(rename_all = "snake_case")]
428pub enum GridLength {
429 #[default]
431 Shortest,
432 LongestFreeze,
434 LongestLoop,
436}
437
438#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
440#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
441#[serde(deny_unknown_fields)]
442pub struct Media {
443 #[serde(default, skip_serializing_if = "Option::is_none")]
445 pub id: Option<String>,
446 pub path: String,
448 #[serde(default, skip_serializing_if = "Option::is_none")]
450 pub kind: Option<FileKind>,
451 #[serde(default, skip_serializing_if = "Option::is_none")]
453 pub trim_start: Option<f64>,
454 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub trim_end: Option<f64>,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub speed: Option<f64>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub preserve_pitch: Option<bool>,
464 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
466 pub r#loop: bool,
467 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub volume: Option<f32>,
470 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
472 pub muted: bool,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub opacity: Option<f32>,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub fade_in: Option<f64>,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub fade_out: Option<f64>,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub zoom: Option<[f32; 2]>,
485 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub blur: Option<f32>,
488 #[serde(default, skip_serializing_if = "Option::is_none")]
490 pub label: Option<String>,
491 #[serde(default, skip_serializing_if = "Option::is_none")]
493 pub at: Option<f64>,
494 #[serde(default, skip_serializing_if = "Option::is_none")]
496 pub duration: Option<f64>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub inset: Option<Inset>,
500 #[serde(default, skip_serializing_if = "Option::is_none")]
502 pub fit: Option<Fit>,
503 #[serde(default, skip_serializing_if = "Option::is_none")]
505 pub align: Option<Align>,
506}
507
508impl Media {
509 #[must_use]
511 pub fn new(path: impl Into<String>) -> Self {
512 Self {
513 id: None,
514 path: path.into(),
515 kind: None,
516 trim_start: None,
517 trim_end: None,
518 speed: None,
519 preserve_pitch: None,
520 r#loop: false,
521 volume: None,
522 muted: false,
523 opacity: None,
524 fade_in: None,
525 fade_out: None,
526 zoom: None,
527 blur: None,
528 label: None,
529 at: None,
530 duration: None,
531 inset: None,
532 fit: None,
533 align: None,
534 }
535 }
536}
537
538#[derive(Clone, Debug, PartialEq, Serialize)]
540#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
541#[serde(untagged)]
542pub enum MediaItem {
543 Path(String),
545 Node(Box<Node>),
547}
548
549#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
551#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
552#[serde(deny_unknown_fields)]
553pub struct Text {
554 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub id: Option<String>,
557 pub text: String,
559 #[serde(default)]
561 pub style: TextRole,
562 #[serde(default, skip_serializing_if = "Option::is_none")]
564 pub font_size: Option<f32>,
565 #[serde(default, skip_serializing_if = "Option::is_none")]
567 pub font_family: Option<String>,
568 #[serde(default, skip_serializing_if = "Option::is_none")]
570 pub color: Option<Rgba>,
571 #[serde(default, skip_serializing_if = "Option::is_none")]
573 pub background: Option<Rgba>,
574 #[serde(default, skip_serializing_if = "Vec::is_empty")]
577 pub strokes: Vec<TextStroke>,
578 #[serde(default, skip_serializing_if = "Vec::is_empty")]
581 pub shadows: Vec<TextShadow>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
585 pub max_lines: Option<u32>,
586 #[serde(default, skip_serializing_if = "Option::is_none")]
588 pub opacity: Option<f32>,
589 #[serde(default, skip_serializing_if = "Option::is_none")]
591 pub fade: Option<f64>,
592 #[serde(default, skip_serializing_if = "Option::is_none")]
594 pub at: Option<f64>,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
597 pub duration: Option<f64>,
598 #[serde(default, skip_serializing_if = "Option::is_none")]
600 pub inset: Option<Inset>,
601 #[serde(default, skip_serializing_if = "Option::is_none")]
603 pub align: Option<Align>,
604}
605
606#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
608#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
609#[serde(rename_all = "snake_case")]
610pub enum TextRole {
611 Title,
613 Subtitle,
615 #[default]
617 Body,
618 Label,
620}
621
622#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
624#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
625#[serde(deny_unknown_fields)]
626pub struct ColorFill {
627 #[serde(default, skip_serializing_if = "Option::is_none")]
629 pub id: Option<String>,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub color: Option<Rgba>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub at: Option<f64>,
636 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub duration: Option<f64>,
639 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub inset: Option<Inset>,
642}
643
644#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
646#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
647#[cfg_attr(feature = "schema", schemars(rename = "CaptionsNode"))]
648#[serde(deny_unknown_fields)]
649pub struct Captions {
650 #[serde(default, skip_serializing_if = "Option::is_none")]
652 pub id: Option<String>,
653 #[serde(default, skip_serializing_if = "Vec::is_empty")]
655 pub cues: Vec<Cue>,
656 #[serde(default, skip_serializing_if = "Vec::is_empty")]
659 pub words: Vec<Cue>,
660 #[serde(default, skip_serializing_if = "Option::is_none")]
662 pub file: Option<String>,
663 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub srt: Option<String>,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
668 pub vtt: Option<String>,
669 #[serde(default)]
671 pub style: CaptionStyle,
672 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub max_chars: Option<u32>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub font_size: Option<f32>,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub color: Option<Rgba>,
682 #[serde(default, skip_serializing_if = "Option::is_none")]
684 pub highlight: Option<Rgba>,
685 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
688 pub speakers: BTreeMap<String, SpeakerLook>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub position: Option<f32>,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
695 pub at: Option<f64>,
696 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub duration: Option<f64>,
699}
700
701#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
703#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
704#[serde(deny_unknown_fields)]
705pub struct Cue {
706 pub start: f64,
708 pub end: f64,
710 pub text: String,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
714 pub speaker: Option<String>,
715}
716
717#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
719#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
720#[serde(deny_unknown_fields)]
721pub struct SpeakerLook {
722 #[serde(default, skip_serializing_if = "Option::is_none")]
724 pub color: Option<Rgba>,
725 #[serde(default, skip_serializing_if = "Option::is_none")]
727 pub position: Option<alpha2_model::Vec2>,
728}
729
730#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
732#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
733#[serde(rename_all = "kebab-case")]
734pub enum CaptionStyle {
735 #[default]
737 Outline,
738 Boxed,
740 BarBottom,
743 BoldCenter,
745 Bold,
747 Karaoke,
750 Pop,
752}
753
754#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
756#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
757#[serde(deny_unknown_fields)]
758pub struct Layout {
759 #[serde(default, skip_serializing_if = "Option::is_none")]
761 pub id: Option<String>,
762 pub root: Box<LayoutNode>,
764 #[serde(default, skip_serializing_if = "Option::is_none")]
766 pub at: Option<f64>,
767 #[serde(default, skip_serializing_if = "Option::is_none")]
769 pub duration: Option<f64>,
770 #[serde(default, skip_serializing_if = "Option::is_none")]
772 pub inset: Option<Inset>,
773 #[serde(default, skip_serializing_if = "Option::is_none")]
775 pub align: Option<Align>,
776}
777
778#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
780#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
781#[serde(deny_unknown_fields)]
782pub struct Audio {
783 #[serde(default, skip_serializing_if = "Option::is_none")]
785 pub id: Option<String>,
786 pub path: String,
788 #[serde(default, skip_serializing_if = "Option::is_none")]
790 pub trim_start: Option<f64>,
791 #[serde(default, skip_serializing_if = "Option::is_none")]
793 pub trim_end: Option<f64>,
794 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
796 pub r#loop: bool,
797 #[serde(default, skip_serializing_if = "Option::is_none")]
799 pub volume: Option<f32>,
800 #[serde(default, skip_serializing_if = "Option::is_none")]
802 pub fade_in: Option<f64>,
803 #[serde(default, skip_serializing_if = "Option::is_none")]
805 pub fade_out: Option<f64>,
806 #[serde(default, skip_serializing_if = "Option::is_none")]
808 pub duck_under: Option<String>,
809 #[serde(default, skip_serializing_if = "Option::is_none")]
811 pub duck_depth_db: Option<f32>,
812 #[serde(default, skip_serializing_if = "Option::is_none")]
814 pub at: Option<f64>,
815 #[serde(default, skip_serializing_if = "Option::is_none")]
817 pub duration: Option<f64>,
818}
819
820#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
822#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
823#[serde(deny_unknown_fields)]
824pub struct Raw {
825 #[serde(default, skip_serializing_if = "Vec::is_empty")]
828 pub sources: Vec<MediaSource>,
829 #[serde(default, skip_serializing_if = "Vec::is_empty")]
831 pub tracks: Vec<Track>,
832 #[serde(default, skip_serializing_if = "Vec::is_empty")]
834 pub compositions: Vec<Composition>,
835 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
837 pub assets: BTreeMap<String, String>,
838}
839
840#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
842#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
843#[serde(deny_unknown_fields)]
844pub struct Slideshow {
845 #[serde(default, skip_serializing_if = "Option::is_none")]
847 pub id: Option<String>,
848 pub items: Vec<MediaItem>,
850 #[serde(default, skip_serializing_if = "Option::is_none")]
853 pub seconds: Option<f64>,
854 #[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")]
879 pub id: Option<String>,
880 pub video: MediaItem,
882 pub captions: Box<Captions>,
884 #[serde(default, skip_serializing_if = "Option::is_none")]
886 pub fit: Option<Fit>,
887 #[serde(default, skip_serializing_if = "Option::is_none")]
889 pub at: Option<f64>,
890}
891
892#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
894#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
895#[serde(deny_unknown_fields)]
896pub struct Title {
897 #[serde(default, skip_serializing_if = "Option::is_none")]
899 pub id: Option<String>,
900 pub text: String,
902 #[serde(default, skip_serializing_if = "Option::is_none")]
904 pub subtitle: Option<String>,
905 #[serde(default, skip_serializing_if = "Option::is_none")]
907 pub align: Option<Align>,
908 #[serde(default, skip_serializing_if = "Option::is_none")]
910 pub fade: Option<f64>,
911 #[serde(default, skip_serializing_if = "Option::is_none")]
913 pub background: Option<Rgba>,
914 #[serde(default, skip_serializing_if = "Option::is_none")]
916 pub at: Option<f64>,
917 #[serde(default, skip_serializing_if = "Option::is_none")]
919 pub duration: Option<f64>,
920}
921
922#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
924#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
925#[serde(deny_unknown_fields)]
926pub struct LowerThird {
927 #[serde(default, skip_serializing_if = "Option::is_none")]
929 pub id: Option<String>,
930 pub text: String,
932 #[serde(default, skip_serializing_if = "Option::is_none")]
934 pub subtitle: Option<String>,
935 #[serde(default, skip_serializing_if = "Option::is_none")]
937 pub fade: Option<f64>,
938 #[serde(default, skip_serializing_if = "Option::is_none")]
940 pub at: Option<f64>,
941 #[serde(default, skip_serializing_if = "Option::is_none")]
943 pub duration: Option<f64>,
944}
945
946#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
948#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
949#[serde(deny_unknown_fields)]
950pub struct EndCard {
951 #[serde(default, skip_serializing_if = "Option::is_none")]
953 pub id: Option<String>,
954 pub text: String,
956 #[serde(default, skip_serializing_if = "Option::is_none")]
958 pub subtitle: Option<String>,
959 #[serde(default, skip_serializing_if = "Option::is_none")]
961 pub logo: Option<String>,
962 #[serde(default, skip_serializing_if = "Option::is_none")]
964 pub background: Option<Rgba>,
965 #[serde(default, skip_serializing_if = "Option::is_none")]
967 pub fade: Option<f64>,
968 #[serde(default, skip_serializing_if = "Option::is_none")]
970 pub at: Option<f64>,
971 #[serde(default, skip_serializing_if = "Option::is_none")]
973 pub duration: Option<f64>,
974}
975
976#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
978#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
979#[serde(deny_unknown_fields)]
980pub struct ComparisonGrid {
981 #[serde(default, skip_serializing_if = "Option::is_none")]
983 pub id: Option<String>,
984 pub items: Vec<MediaItem>,
986 #[serde(default, skip_serializing_if = "Option::is_none")]
988 pub columns: Option<u32>,
989 #[serde(default)]
991 pub gap: f32,
992 #[serde(default)]
994 pub length: GridLength,
995 #[serde(default, skip_serializing_if = "Option::is_none")]
997 pub audio_from: Option<u32>,
998 #[serde(default, skip_serializing_if = "Option::is_none")]
1000 pub at: Option<f64>,
1001 #[serde(default, skip_serializing_if = "Option::is_none")]
1003 pub duration: Option<f64>,
1004}
1005
1006#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1008#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1009#[serde(deny_unknown_fields)]
1010pub struct Reframe {
1011 #[serde(default, skip_serializing_if = "Option::is_none")]
1013 pub id: Option<String>,
1014 pub video: MediaItem,
1016 #[serde(default, skip_serializing_if = "Option::is_none")]
1018 pub blur: Option<f32>,
1019 #[serde(default, skip_serializing_if = "Option::is_none")]
1021 pub at: Option<f64>,
1022}
1023
1024#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1026#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1027#[serde(deny_unknown_fields)]
1028pub struct Logo {
1029 #[serde(default, skip_serializing_if = "Option::is_none")]
1031 pub id: Option<String>,
1032 pub path: String,
1034 #[serde(default, skip_serializing_if = "Option::is_none")]
1036 pub corner: Option<Align>,
1037 #[serde(default, skip_serializing_if = "Option::is_none")]
1039 pub size: Option<f32>,
1040 #[serde(default, skip_serializing_if = "Option::is_none")]
1042 pub margin: Option<f32>,
1043 #[serde(default, skip_serializing_if = "Option::is_none")]
1045 pub opacity: Option<f32>,
1046 #[serde(default, skip_serializing_if = "Option::is_none")]
1048 pub at: Option<f64>,
1049 #[serde(default, skip_serializing_if = "Option::is_none")]
1051 pub duration: Option<f64>,
1052}
1053
1054#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1056#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1057#[serde(deny_unknown_fields)]
1058pub struct MusicBed {
1059 #[serde(default, skip_serializing_if = "Option::is_none")]
1061 pub id: Option<String>,
1062 pub path: String,
1064 #[serde(default, skip_serializing_if = "Option::is_none")]
1066 pub volume: Option<f32>,
1067 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 pub fade_in: Option<f64>,
1070 #[serde(default, skip_serializing_if = "Option::is_none")]
1072 pub fade_out: Option<f64>,
1073 #[serde(default, skip_serializing_if = "Option::is_none")]
1075 pub duck_under: Option<String>,
1076 #[serde(default, skip_serializing_if = "Option::is_none")]
1078 pub duck_depth_db: Option<f32>,
1079 #[serde(default = "yes")]
1081 pub r#loop: bool,
1082 #[serde(default, skip_serializing_if = "Option::is_none")]
1084 pub at: Option<f64>,
1085 #[serde(default, skip_serializing_if = "Option::is_none")]
1087 pub duration: Option<f64>,
1088}
1089
1090const fn yes() -> bool {
1091 true
1092}