1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
use std::path::PathBuf;
use animation::{has_keyframes_for_path, resolve_number_at_time};
use scene::{
AudioElement, BaseTimelineElement, Element, ElementAnimations, ParamValue, RetimeConfig,
SceneTracks, Track,
};
use time::MediaTime;
pub const MIN_RETIME_RATE: f64 = 0.01;
pub const MAX_RETIME_RATE: f64 = 5.0;
pub const VOLUME_DB_MIN: f64 = -60.0;
pub const VOLUME_DB_MAX: f64 = 20.0;
pub fn clamp_retime_rate(rate: f64) -> f64 {
if !rate.is_finite() || rate <= 0.0 {
return 1.0;
}
rate.clamp(MIN_RETIME_RATE, MAX_RETIME_RATE)
}
pub fn source_time_at_clip_time(clip_time: f64, retime: Option<&RetimeConfig>) -> f64 {
clip_time * clamp_retime_rate(retime.map(|config| config.rate).unwrap_or(1.0))
}
pub fn clamp_db(value: f64) -> f64 {
if !value.is_finite() {
return 0.0;
}
value.clamp(VOLUME_DB_MIN, VOLUME_DB_MAX)
}
pub fn db_to_linear(db: f64) -> f64 {
10_f64.powf(clamp_db(db) / 20.0)
}
#[derive(Debug, Clone)]
pub struct AudibleClip {
pub element_id: String,
pub path: PathBuf,
pub start_time: MediaTime,
pub duration: MediaTime,
pub trim_start: MediaTime,
pub trim_end: MediaTime,
pub volume_db: f64,
pub retime: Option<RetimeConfig>,
pub animations: Option<ElementAnimations>,
}
impl AudibleClip {
pub fn end_time(&self) -> MediaTime {
self.start_time + self.duration
}
pub fn effective_rate(&self) -> f64 {
clamp_retime_rate(self.retime.map(|config| config.rate).unwrap_or(1.0))
}
pub fn requires_maintain_pitch(&self) -> bool {
self.retime.is_some_and(|config| {
config.maintain_pitch == Some(true)
&& config.rate.is_finite()
&& config.rate > 0.0
&& clamp_retime_rate(config.rate) != 1.0
})
}
pub fn has_animated_volume(&self) -> bool {
has_keyframes_for_path(self.animations.as_ref(), "volume")
}
pub fn gain_at(&self, local_time: MediaTime) -> f64 {
db_to_linear(resolve_number_at_time(
self.volume_db,
self.animations.as_ref(),
"volume",
local_time,
))
}
pub fn static_gain(&self) -> f64 {
db_to_linear(self.volume_db)
}
}
fn track_audio_muted(track: &Track) -> bool {
match track {
Track::Video { muted, .. } | Track::Audio { muted, .. } => *muted,
_ => false,
}
}
fn element_source(element: &Element) -> Option<(&BaseTimelineElement, &str, Option<RetimeConfig>)> {
match element {
Element::Audio(AudioElement::Upload(upload)) => {
Some((&upload.base, upload.media_id.as_str(), upload.retime))
}
Element::Audio(AudioElement::Library(library)) => {
Some((&library.base, library.source_url.as_str(), library.retime))
}
Element::Video(video) => {
if video.hidden == Some(true) || video.is_source_audio_enabled == Some(false) {
return None;
}
Some((&video.base, video.media_id.as_str(), video.retime))
}
_ => None,
}
}
fn collect_from_track(
track: &Track,
media: &dyn Fn(&str) -> Option<PathBuf>,
range: (MediaTime, MediaTime),
clips: &mut Vec<AudibleClip>,
) {
if track_audio_muted(track) {
return;
}
for element in track.elements() {
let Some((base, source_key, retime)) = element_source(element) else {
continue;
};
if base.duration <= MediaTime::ZERO {
continue;
}
let end_time = base.start_time + base.duration;
if end_time <= range.0 || base.start_time >= range.1 {
continue;
}
if base.params.get("muted") == Some(&ParamValue::Bool(true)) {
continue;
}
let volume_db = match base.params.get("volume") {
Some(ParamValue::Number(value)) => *value,
_ => 0.0,
};
let Some(path) = media(source_key) else {
continue;
};
clips.push(AudibleClip {
element_id: base.id.clone(),
path,
start_time: base.start_time,
duration: base.duration,
trim_start: base.trim_start,
trim_end: base.trim_end,
volume_db,
retime,
animations: base.animations.clone(),
});
}
}
pub fn collect_audible_elements(
tracks: &SceneTracks,
media: &dyn Fn(&str) -> Option<PathBuf>,
range: (MediaTime, MediaTime),
) -> Vec<AudibleClip> {
let mut clips = Vec::new();
for track in &tracks.overlay {
collect_from_track(track, media, range, &mut clips);
}
collect_from_track(&tracks.main, media, range, &mut clips);
for track in &tracks.audio {
collect_from_track(track, media, range, &mut clips);
}
clips
}
|