1use crate::media::waveform::{Peaks, PEAKS_PER_SEC};
5
6const EPS: f64 = 1e-6;
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct AutoCutParams {
10 pub threshold_db: f32,
12 pub min_silence: f64,
14 pub min_speech: f64,
16 pub padding: f64,
18}
19
20impl Default for AutoCutParams {
21 fn default() -> Self {
22 Self { threshold_db: -35.0, min_silence: 0.4, min_speech: 0.2, padding: 0.1 }
23 }
24}
25
26pub fn loud_segments(peaks: &Peaks, src_in: f64, src_len: f64, params: &AutoCutParams) -> Vec<(f64, f64)> {
30 let per = PEAKS_PER_SEC as f64;
31 let b0 = ((src_in * per).floor().max(0.0)) as usize;
32 let b1 = (((src_in + src_len) * per).ceil().max(0.0) as usize).min(peaks.len());
33 if peaks.is_empty() || src_len <= 0.0 || b0 >= b1 {
34 return Vec::new();
35 }
36 let thr = 10f32.powf(params.threshold_db / 20.0);
38 let mut segs: Vec<(f64, f64)> = Vec::new();
39 let mut run: Option<f64> = None;
40 for i in b0..=b1 {
41 let loud = i < b1 && peaks.min[i].abs().max(peaks.max[i].abs()) > thr;
42 match (loud, run) {
43 (true, None) => run = Some(i as f64 / per),
44 (false, Some(start)) => {
45 let end = i as f64 / per;
46 match segs.last_mut() {
48 Some(last) if start - last.1 < params.min_silence => last.1 = end,
49 _ => segs.push((start, end)),
50 }
51 run = None;
52 }
53 _ => {}
54 }
55 }
56 let (lo, hi) = (src_in, src_in + src_len);
57 let mut out: Vec<(f64, f64)> = Vec::new();
58 for (a, b) in segs {
59 if b - a < params.min_speech {
60 continue;
61 }
62 let (a, b) = ((a - params.padding).max(lo), (b + params.padding).min(hi));
63 if b <= a {
64 continue;
65 }
66 match out.last_mut() {
67 Some(last) if a <= last.1 => last.1 = last.1.max(b), _ => out.push((a, b)),
69 }
70 }
71 out
72}
73
74pub fn to_timeline(
79 segments: &[(f64, f64)],
80 start: f64,
81 src_in: f64,
82 duration: f64,
83 speed: f64,
84 keep_quiet: bool,
85) -> (Vec<f64>, Vec<(f64, f64)>) {
86 let speed = if speed > 0.0 { speed } else { 1.0 };
87 let end = start + duration;
88 let mut loud: Vec<(f64, f64)> = Vec::new();
89 for &(a, b) in segments {
90 let ta = (start + (a - src_in) / speed).max(start);
91 let tb = (start + (b - src_in) / speed).min(end);
92 if tb <= ta {
93 continue;
94 }
95 match loud.last_mut() {
96 Some(last) if ta <= last.1 => last.1 = last.1.max(tb),
97 _ => loud.push((ta, tb)),
98 }
99 }
100 let mut quiet: Vec<(f64, f64)> = Vec::new();
101 let mut pos = start;
102 for &(a, b) in &loud {
103 if a > pos + EPS {
104 quiet.push((pos, a));
105 }
106 pos = b;
107 }
108 if end > pos + EPS {
109 quiet.push((pos, end));
110 }
111 let mut cuts: Vec<f64> =
112 loud.iter().flat_map(|&(a, b)| [a, b]).filter(|&t| t > start + EPS && t < end - EPS).collect();
113 cuts.sort_by(f64::total_cmp);
114 cuts.dedup_by(|a, b| (*a - *b).abs() < EPS);
115 (cuts, if keep_quiet { loud } else { quiet })
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 fn peaks(loud: &[(f64, f64)]) -> Peaks {
124 let n = 1000;
125 let mut min = vec![-0.0001f32; n];
126 let mut max = vec![0.0001f32; n];
127 for &(a, b) in loud {
128 for i in (a * 100.0) as usize..((b * 100.0) as usize).min(n) {
129 min[i] = -0.5;
130 max[i] = 0.5;
131 }
132 }
133 Peaks { min, max }
134 }
135
136 fn close(got: &[(f64, f64)], want: &[(f64, f64)]) -> bool {
137 got.len() == want.len()
138 && got.iter().zip(want).all(|(g, w)| (g.0 - w.0).abs() < 1e-6 && (g.1 - w.1).abs() < 1e-6)
139 }
140
141 #[test]
142 fn segments_merge_drop_pad_clamp() {
143 let p = peaks(&[(1.0, 3.0), (3.2, 4.0), (5.0, 5.1), (8.0, 9.0)]);
144 let params = AutoCutParams::default();
145 let segs = loud_segments(&p, 0.0, 10.0, ¶ms);
147 assert!(close(&segs, &[(0.9, 4.1), (7.9, 9.1)]), "{segs:?}");
148 let segs = loud_segments(&p, 0.95, 8.0, ¶ms);
150 assert!(close(&segs, &[(0.95, 4.1), (7.9, 8.95)]), "{segs:?}");
151 let segs = loud_segments(&p, 0.0, 10.0, &AutoCutParams { threshold_db: -3.0, ..params });
153 assert!(segs.is_empty(), "{segs:?}");
154 let segs = loud_segments(&p, 0.0, 10.0, &AutoCutParams { padding: 2.0, ..params });
156 assert!(close(&segs, &[(0.0, 10.0)]), "{segs:?}");
157 assert!(loud_segments(&Peaks { min: vec![], max: vec![] }, 0.0, 10.0, ¶ms).is_empty());
159 assert!(loud_segments(&p, 0.0, 0.0, ¶ms).is_empty());
160 assert!(loud_segments(&p, 20.0, 5.0, ¶ms).is_empty());
161 }
162
163 #[test]
164 fn timeline_mapping() {
165 let (cuts, quiet) = to_timeline(&[(1.0, 2.0), (3.0, 4.0)], 10.0, 0.0, 5.0, 1.0, false);
167 assert_eq!(cuts, vec![11.0, 12.0, 13.0, 14.0]);
168 assert!(close(&quiet, &[(10.0, 11.0), (12.0, 13.0), (14.0, 15.0)]), "{quiet:?}");
169 let (cuts2, remove) = to_timeline(&[(1.0, 2.0), (3.0, 4.0)], 10.0, 0.0, 5.0, 1.0, true);
171 assert_eq!(cuts2, cuts);
172 assert!(close(&remove, &[(11.0, 12.0), (13.0, 14.0)]), "{remove:?}");
173 let (cuts, quiet) = to_timeline(&[(1.0, 3.0), (5.0, 9.0)], 0.0, 1.0, 4.0, 2.0, false);
175 assert_eq!(cuts, vec![1.0, 2.0]);
176 assert!(close(&quiet, &[(1.0, 2.0)]), "{quiet:?}");
177 let (cuts, quiet) = to_timeline(&[(20.0, 30.0)], 0.0, 0.0, 5.0, 1.0, false);
179 assert!(cuts.is_empty());
180 assert!(close(&quiet, &[(0.0, 5.0)]), "{quiet:?}");
181 let (cuts, quiet) = to_timeline(&[(0.0, 5.0)], 0.0, 0.0, 5.0, 1.0, false);
183 assert!(cuts.is_empty() && quiet.is_empty());
184 }
185}