1pub mod metadata;
24pub mod place;
25pub mod project;
26pub mod schema;
27pub mod time;
28
29use std::path::Path;
30
31pub use metadata::{FFCINE, FFCINE_VERSION, HasMetadata};
32pub use place::{Placed, PlacedMarker};
33pub use project::{
34 BusDuck, BusParams, BusReverb, BusSend, ClipGrade, GradeParams, MixParams, Output, OutputMode,
35 Project, SourceKind,
36};
37pub use schema::{
38 Clip, DEFAULT_MEDIA_KEY, Effect, ExternalReference, Gap, GeneratorReference, GenericEffect,
39 Item, LinearTimeWarp, Marker, MediaReference, MissingReference, Stack, Timeline, Track,
40 Transition, track_kind, transition_type,
41};
42pub use time::{RationalTime, TimeRange};
43
44use ffcine_core::{Error, Result};
45
46impl Timeline {
47 pub fn new(name: impl Into<String>) -> Self {
48 Self {
49 name: name.into(),
50 ..Self::default()
51 }
52 }
53
54 pub fn from_json(json: &str) -> Result<Self> {
55 Ok(serde_json::from_str(json)?)
56 }
57
58 pub fn to_json(&self) -> Result<String> {
60 let mut out = Vec::new();
61 let fmt = serde_json::ser::PrettyFormatter::with_indent(b" ");
62 let mut ser = serde_json::Serializer::with_formatter(&mut out, fmt);
63 serde::Serialize::serialize(self, &mut ser)?;
64 out.push(b'\n');
65 Ok(String::from_utf8(out).unwrap_or_default())
66 }
67
68 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
69 let path = path.as_ref();
70 let json = std::fs::read_to_string(path).map_err(|source| Error::Io {
71 path: path.to_owned(),
72 source,
73 })?;
74 Self::from_json(&json)
75 }
76
77 pub fn to_file(&self, path: impl AsRef<Path>) -> Result<()> {
78 let path = path.as_ref();
79 std::fs::write(path, self.to_json()?).map_err(|source| Error::Io {
80 path: path.to_owned(),
81 source,
82 })
83 }
84
85 pub fn project(&self) -> Option<Result<Project>> {
87 self.ffcine_get("project")
88 }
89
90 pub fn set_project(&mut self, project: &Project) -> Result<()> {
92 self.ffcine_mut().insert("v".into(), FFCINE_VERSION.into());
93 self.ffcine_set("project", project)
94 }
95
96 pub fn track_list(&self) -> impl Iterator<Item = &Track> {
98 self.tracks.children.iter().filter_map(|c| match c {
99 Item::Track(t) => Some(t),
100 _ => None,
101 })
102 }
103
104 pub fn clips(&self) -> Vec<&Clip> {
106 let mut out = Vec::new();
107 collect_clips(&self.tracks.children, &mut out);
108 out
109 }
110}
111
112fn collect_clips<'a>(items: &'a [Item], out: &mut Vec<&'a Clip>) {
113 for item in items {
114 match item {
115 Item::Clip(c) => out.push(c),
116 Item::Stack(s) => collect_clips(&s.children, out),
117 Item::Track(t) => collect_clips(&t.children, out),
118 Item::Gap(_) | Item::Transition(_) | Item::Unknown(_) => {}
119 }
120 }
121}
122
123impl Item {
124 pub fn name(&self) -> &str {
125 match self {
126 Item::Clip(x) => &x.name,
127 Item::Gap(x) => &x.name,
128 Item::Stack(x) => &x.name,
129 Item::Track(x) => &x.name,
130 Item::Transition(x) => &x.name,
131 Item::Unknown(m) => m
132 .get("name")
133 .and_then(serde_json::Value::as_str)
134 .unwrap_or(""),
135 }
136 }
137
138 pub fn source_range(&self) -> Option<&TimeRange> {
139 match self {
140 Item::Clip(x) => x.source_range.as_ref(),
141 Item::Gap(x) => x.source_range.as_ref(),
142 Item::Stack(x) => x.source_range.as_ref(),
143 Item::Track(x) => x.source_range.as_ref(),
144 Item::Transition(_) | Item::Unknown(_) => None,
145 }
146 }
147}