aviutl2_alias/
track.rs

1/// トラックバーのフラグ。
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
3pub struct TrackFlags {
4    /// 「加速」相当。
5    ease_in: bool,
6    /// 「減速」相当。
7    ease_out: bool,
8    /// 「中間点無視」相当。
9    twopoints: bool,
10}
11
12/// トラックバーの移動単位。
13#[derive(Clone, Copy, PartialEq, Eq, Hash)]
14pub enum TrackStep {
15    /// 1。
16    One,
17    /// 0.1。
18    PointOne,
19    /// 0.01。
20    PointZeroOne,
21    /// 0.001。
22    PointZeroZeroOne,
23}
24
25/// トラックバーの移動単位のパースエラー。
26#[derive(Debug, Clone, thiserror::Error)]
27#[non_exhaustive]
28pub enum TrackStepParseError {
29    #[error("invalid track step precision")]
30    InvalidPrecision,
31    #[error("failed to parse track step value")]
32    ParseError(#[from] std::num::ParseFloatError),
33}
34
35impl TrackStep {
36    /// パースして精度と値を取得します。
37    pub fn parse_and_get(value: &str) -> Result<(TrackStep, f64), TrackStepParseError> {
38        let maybe_negative = value.starts_with('-');
39        let abs_value_str = if maybe_negative { &value[1..] } else { value };
40        let v: f64 = abs_value_str.parse()?;
41        let dot_index = abs_value_str.find('.');
42        let step = match dot_index {
43            None => TrackStep::One,
44            Some(idx) => match abs_value_str.len() - idx - 1 {
45                1 => TrackStep::PointOne,
46                2 => TrackStep::PointZeroOne,
47                3 => TrackStep::PointZeroZeroOne,
48                _ => return Err(TrackStepParseError::InvalidPrecision),
49            },
50        };
51        let final_value = if maybe_negative { -v } else { v };
52        Ok((step, final_value))
53    }
54
55    /// 値を特定の精度に丸めて文字列化します。
56    pub fn round_to_string(&self, value: f64) -> String {
57        match self {
58            TrackStep::One => format!("{}", value.round() as i64),
59            TrackStep::PointOne => format!("{:.1}", value),
60            TrackStep::PointZeroOne => format!("{:.2}", value),
61            TrackStep::PointZeroZeroOne => format!("{:.3}", value),
62        }
63    }
64}
65
66impl TryFrom<f64> for TrackStep {
67    type Error = ();
68
69    fn try_from(value: f64) -> Result<Self, Self::Error> {
70        match value {
71            1.0 => Ok(TrackStep::One),
72            0.1 => Ok(TrackStep::PointOne),
73            0.01 => Ok(TrackStep::PointZeroOne),
74            0.001 => Ok(TrackStep::PointZeroZeroOne),
75            _ => Err(()),
76        }
77    }
78}
79impl From<TrackStep> for f64 {
80    fn from(step: TrackStep) -> Self {
81        match step {
82            TrackStep::One => 1.0,
83            TrackStep::PointOne => 0.1,
84            TrackStep::PointZeroOne => 0.01,
85            TrackStep::PointZeroZeroOne => 0.001,
86        }
87    }
88}
89impl TrackStep {
90    /// トラックバーのステップ値を取得します。
91    pub fn value(&self) -> f64 {
92        (*self).into()
93    }
94}
95
96impl std::fmt::Display for TrackStep {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        match self {
99            TrackStep::One => write!(f, "1"),
100            TrackStep::PointOne => write!(f, "0.1"),
101            TrackStep::PointZeroOne => write!(f, "0.01"),
102            TrackStep::PointZeroZeroOne => write!(f, "0.001"),
103        }
104    }
105}
106impl std::fmt::Debug for TrackStep {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "TrackStep({})", f64::from(*self))
109    }
110}
111
112/// 時間制御のカーブ。
113#[derive(Debug, Clone, PartialEq)]
114pub struct TimeCurve {
115    /// カーブの制御点。
116    pub control_points: Vec<TimeCurvePoint>,
117}
118
119impl std::fmt::Display for TimeCurve {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        if self.control_points.is_empty() {
122            return write!(f, "");
123        }
124        if self.control_points.len() == 2
125            && self.control_points[0].position == 0.0
126            && self.control_points[0].value == 0.0
127            && self.control_points[1].position == 1.0
128            && self.control_points[1].value == 1.0
129        {
130            return write!(
131                f,
132                "{},{},{},{}",
133                self.control_points[0].right_handle.0,
134                self.control_points[0].right_handle.1,
135                self.control_points[1].right_handle.0,
136                self.control_points[1].right_handle.1
137            );
138        }
139        let parts: Vec<String> = self
140            .control_points
141            .iter()
142            .map(|pt| {
143                format!(
144                    "{},{},{},{}",
145                    pt.position, pt.value, pt.right_handle.0, pt.right_handle.1
146                )
147            })
148            .collect();
149        write!(f, "{}", parts.join(","))
150    }
151}
152
153/// 時間制御カーブの制御点。
154#[derive(Debug, Clone, PartialEq)]
155pub struct TimeCurvePoint {
156    /// 時間軸の位置(0.0〜1.0)。
157    pub position: f64,
158    /// 値(0.0〜1.0)。
159    pub value: f64,
160    /// 右ハンドルの相対位置。
161    ///
162    /// # Note
163    ///
164    /// 左ハンドルはこの点の数値を反転した位置にあります。
165    pub right_handle: (f64, f64),
166}
167
168impl Default for TimeCurve {
169    fn default() -> Self {
170        TimeCurve {
171            control_points: vec![
172                TimeCurvePoint {
173                    position: 0.0,
174                    value: 0.0,
175                    right_handle: (0.25, 0.25),
176                },
177                TimeCurvePoint {
178                    position: 1.0,
179                    value: 1.0,
180                    right_handle: (0.25, 0.25),
181                },
182            ],
183        }
184    }
185}
186
187/// 時間制御カーブのパースエラー。
188#[derive(Debug, Clone, thiserror::Error)]
189#[non_exhaustive]
190pub enum TimeCurveParseError {
191    #[error("invalid format")]
192    InvalidFormat,
193
194    #[error("invalid number of components ({0})")]
195    InvalidNumComponents(usize),
196
197    #[error("value out of range")]
198    ValueOutOfRange,
199
200    #[error("control points are not in increasing order")]
201    BadPositionOrder,
202}
203
204impl std::str::FromStr for TimeCurve {
205    type Err = TimeCurveParseError;
206
207    fn from_str(s: &str) -> Result<Self, Self::Err> {
208        let parts: Vec<f64> = s
209            .split(',')
210            .map(|part| {
211                part.parse::<f64>()
212                    .map_err(|_| TimeCurveParseError::InvalidFormat)
213            })
214            .collect::<Result<_, _>>()?;
215        match parts.len() {
216            n if n % 4 != 0 => Err(TimeCurveParseError::InvalidNumComponents(n)),
217            0 => Ok(TimeCurve::default()),
218            4 => Ok(TimeCurve {
219                control_points: vec![
220                    TimeCurvePoint {
221                        position: 0.0,
222                        value: 0.0,
223                        right_handle: (parts[0], parts[1]),
224                    },
225                    TimeCurvePoint {
226                        position: 1.0,
227                        value: 1.0,
228                        right_handle: (parts[2], parts[3]),
229                    },
230                ],
231            }),
232            _ => {
233                let control_points = parts
234                    .chunks(4)
235                    .map(|chunk| {
236                        if chunk[0] < 0.0
237                            || chunk[0] > 1.0
238                            || chunk[1] < 0.0
239                            || chunk[1] > 1.0
240                            || chunk[2] < 0.0
241                        {
242                            return Err(TimeCurveParseError::ValueOutOfRange);
243                        }
244                        Ok(TimeCurvePoint {
245                            position: chunk[0],
246                            value: chunk[1],
247                            right_handle: (chunk[2], chunk[3]),
248                        })
249                    })
250                    .collect::<Result<Vec<_>, _>>()?;
251                if !control_points
252                    .windows(2)
253                    .all(|w| w[0].position < w[1].position)
254                {
255                    return Err(TimeCurveParseError::BadPositionOrder);
256                }
257                Ok(TimeCurve { control_points })
258            }
259        }
260    }
261}
262
263/// `.aup2`に保存されるトラックバー項目を表します。
264#[derive(Debug, Clone, PartialEq)]
265pub enum TrackItem {
266    /// スクリプトを伴わない単一値トラック。
267    Static(StaticTrackItem),
268    /// スクリプトや時間制御を含んだトラックバー項目。
269    Animated(AnimatedTrackItem),
270}
271
272impl std::fmt::Display for TrackItem {
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        match self {
275            TrackItem::Static(item) => write!(f, "{}", item),
276            TrackItem::Animated(item) => write!(f, "{}", item),
277        }
278    }
279}
280
281impl std::str::FromStr for TrackItem {
282    type Err = TrackItemParseError;
283
284    fn from_str(s: &str) -> Result<Self, Self::Err> {
285        if s.contains(',') {
286            let animated: AnimatedTrackItem = s.parse()?;
287            Ok(TrackItem::Animated(animated))
288        } else {
289            let static_item: StaticTrackItem = s.parse()?;
290            Ok(TrackItem::Static(static_item))
291        }
292    }
293}
294
295/// スクリプトを伴わない単一値トラックを表します。
296#[derive(Debug, Clone, PartialEq)]
297pub struct StaticTrackItem {
298    /// 移動単位。
299    pub step: TrackStep,
300
301    /// 値。
302    pub value: f64,
303}
304
305impl std::fmt::Display for StaticTrackItem {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        let value_str = self.step.round_to_string(self.value);
308        write!(f, "{}", value_str)
309    }
310}
311
312impl std::str::FromStr for StaticTrackItem {
313    type Err = TrackStepParseError;
314
315    fn from_str(s: &str) -> Result<Self, Self::Err> {
316        let (step, value) = TrackStep::parse_and_get(s)?;
317        Ok(StaticTrackItem { step, value })
318    }
319}
320
321/// スクリプトや時間制御を含んだトラックバー項目です。
322#[derive(Debug, Clone, PartialEq)]
323pub struct AnimatedTrackItem {
324    /// 移動単位。
325    pub step: TrackStep,
326    /// 中間点ごとの値。
327    pub values: Vec<f64>,
328
329    /// フラグ。
330    pub flags: TrackFlags,
331    /// トラックバースクリプトの名前。
332    pub script_name: String,
333    /// 設定値。
334    pub parameter: Option<f64>,
335    /// 時間制御のカーブ。
336    pub time_curve: Option<TimeCurve>,
337}
338
339/// トラックバー項目のパースエラー。
340#[derive(Debug, Clone, thiserror::Error)]
341#[non_exhaustive]
342pub enum TrackItemParseError {
343    #[error("invalid segments count")]
344    InvalidNumSegments(usize),
345
346    #[error("invalid elements count")]
347    InvalidNumElements(usize),
348
349    #[error("failed to parse element")]
350    ElementParseError(#[from] std::num::ParseFloatError),
351
352    #[error("failed to parse curve")]
353    TimeCurveParseError(#[from] TimeCurveParseError),
354
355    #[error("invalid flag value")]
356    InvalidFlagValue,
357
358    #[error("inconsistent step")]
359    InconsistentStep,
360
361    #[error("invalid step value")]
362    InvalidStepValue(#[from] TrackStepParseError),
363}
364
365impl std::fmt::Display for AnimatedTrackItem {
366    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367        let mut elements = Vec::new();
368        for value in &self.values {
369            elements.push(self.step.round_to_string(*value));
370        }
371        elements.push(self.script_name.clone());
372        let flags_value = (if self.flags.ease_in { 0b0001 } else { 0 })
373            | (if self.flags.ease_out { 0b0010 } else { 0 })
374            | (if self.flags.twopoints { 0b0100 } else { 0 });
375        elements.push(flags_value.to_string());
376        let mut result = elements.join(",");
377        if let Some(param) = self.parameter {
378            result.push('|');
379            result.push_str(&param.to_string());
380        }
381        if let Some(curve) = &self.time_curve {
382            result.push('|');
383            result.push_str(&curve.to_string());
384        }
385        write!(f, "{}", result)
386    }
387}
388
389impl std::str::FromStr for AnimatedTrackItem {
390    type Err = TrackItemParseError;
391
392    fn from_str(s: &str) -> Result<Self, Self::Err> {
393        let segments = s.split('|').collect::<Vec<&str>>();
394        let items = segments[0].split(",").collect::<Vec<&str>>();
395        if items.len() < 4 {
396            return Err(TrackItemParseError::InvalidNumElements(items.len()));
397        }
398        let flags_value: u8 = items[items.len() - 1]
399            .parse()
400            .map_err(|_| TrackItemParseError::InvalidFlagValue)?;
401        let flags = TrackFlags {
402            ease_in: (flags_value & 0b0001) != 0,
403            ease_out: (flags_value & 0b0010) != 0,
404            twopoints: (flags_value & 0b0100) != 0,
405        };
406        let (parameter, time_curve) = match segments.len() {
407            1 => (None, None),
408            2 if segments[1].contains(',') => {
409                let time_curve: TimeCurve = segments[1]
410                    .parse()
411                    .map_err(TrackItemParseError::TimeCurveParseError)?;
412                (None, Some(time_curve))
413            }
414            2 => {
415                let parameter: f64 = segments[1]
416                    .parse()
417                    .map_err(TrackItemParseError::ElementParseError)?;
418                (Some(parameter), None)
419            }
420            3 => {
421                let parameter: f64 = segments[1]
422                    .parse()
423                    .map_err(TrackItemParseError::ElementParseError)?;
424                let time_curve: TimeCurve = segments[2]
425                    .parse()
426                    .map_err(TrackItemParseError::TimeCurveParseError)?;
427                (Some(parameter), Some(time_curve))
428            }
429            n => {
430                return Err(TrackItemParseError::InvalidNumSegments(n));
431            }
432        };
433
434        let script_name = items[items.len() - 2].to_string();
435        let (step, _) = TrackStep::parse_and_get(items[0])?;
436        let mut values = Vec::new();
437        for item in &items[0..items.len() - 2] {
438            let (item_step, value) = TrackStep::parse_and_get(item)?;
439            if item_step != step {
440                return Err(TrackItemParseError::InconsistentStep);
441            }
442            values.push(value);
443        }
444        Ok(AnimatedTrackItem {
445            step,
446            values,
447            flags,
448            script_name,
449            parameter,
450            time_curve,
451        })
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use rstest::rstest;
459
460    #[test]
461    fn test_time_curve_from_str() {
462        let tc: TimeCurve = "0.25,0.25,0.25,0.25".parse().unwrap();
463        assert_eq!(
464            tc.control_points,
465            vec![
466                TimeCurvePoint {
467                    position: 0.0,
468                    value: 0.0,
469                    right_handle: (0.25, 0.25),
470                },
471                TimeCurvePoint {
472                    position: 1.0,
473                    value: 1.0,
474                    right_handle: (0.25, 0.25),
475                },
476            ]
477        );
478        assert_eq!(tc.to_string(), "0.25,0.25,0.25,0.25");
479    }
480
481    #[test]
482    fn test_time_curve_from_str_multiple_points() {
483        let tc: TimeCurve = "0,0,0.25,0,0.5,0.5,0.25,0,1,1,0.25,0".parse().unwrap();
484
485        assert_eq!(
486            tc.control_points,
487            vec![
488                TimeCurvePoint {
489                    position: 0.0,
490                    value: 0.0,
491                    right_handle: (0.25, 0.0),
492                },
493                TimeCurvePoint {
494                    position: 0.5,
495                    value: 0.5,
496                    right_handle: (0.25, 0.0),
497                },
498                TimeCurvePoint {
499                    position: 1.0,
500                    value: 1.0,
501                    right_handle: (0.25, 0.0),
502                },
503            ]
504        );
505
506        assert_eq!(tc.to_string(), "0,0,0.25,0,0.5,0.5,0.25,0,1,1,0.25,0");
507    }
508
509    #[test]
510    fn test_track_item_parse() {
511        let item_str = "0.1,0.2,MyScript,3|1.5|0.25,0.25,0.25,0.25";
512        let animated_item: AnimatedTrackItem = item_str.parse().unwrap();
513        assert_eq!(
514            animated_item,
515            AnimatedTrackItem {
516                step: TrackStep::PointOne,
517                values: vec![0.1, 0.2],
518                flags: TrackFlags {
519                    ease_in: true,
520                    ease_out: true,
521                    twopoints: false,
522                },
523                script_name: "MyScript".to_string(),
524                parameter: Some(1.5),
525                time_curve: Some(TimeCurve {
526                    control_points: vec![
527                        TimeCurvePoint {
528                            position: 0.0,
529                            value: 0.0,
530                            right_handle: (0.25, 0.25),
531                        },
532                        TimeCurvePoint {
533                            position: 1.0,
534                            value: 1.0,
535                            right_handle: (0.25, 0.25),
536                        },
537                    ],
538                }),
539            }
540        );
541        assert_eq!(animated_item.to_string(), item_str);
542    }
543    #[test]
544    fn test_track_item_parse_segments() {
545        let item_str = "0.1,0.2,MyScript,3|1.5|0.25,0.25,0.25,0.25";
546        let animated_item: AnimatedTrackItem = item_str.parse().unwrap();
547        assert_eq!(animated_item.parameter, Some(1.5));
548        assert!(animated_item.time_curve.is_some());
549
550        let item_str_no_curve = "0.1,0.2,MyScript,3|1.5";
551        let animated_item_no_curve: AnimatedTrackItem = item_str_no_curve.parse().unwrap();
552        assert_eq!(animated_item_no_curve.parameter, Some(1.5));
553        assert!(animated_item_no_curve.time_curve.is_none());
554
555        let item_str_no_param = "0.1,0.2,MyScript,3|0.25,0.25,0.25,0.25";
556        let animated_item_no_param: AnimatedTrackItem = item_str_no_param.parse().unwrap();
557        assert!(animated_item_no_param.parameter.is_none());
558        assert!(animated_item_no_param.time_curve.is_some());
559
560        let item_str_only_values = "0.1,0.2,MyScript,3";
561        let animated_item_only_values: AnimatedTrackItem = item_str_only_values.parse().unwrap();
562        assert!(animated_item_only_values.parameter.is_none());
563        assert!(animated_item_only_values.time_curve.is_none());
564    }
565
566    #[rstest]
567    #[case("1", TrackStep::One, 1.0)]
568    #[case("0.1", TrackStep::PointOne, 0.1)]
569    #[case("0.01", TrackStep::PointZeroOne, 0.01)]
570    #[case("0.001", TrackStep::PointZeroZeroOne, 0.001)]
571    #[case("-2.34", TrackStep::PointZeroOne, -2.34)]
572    fn test_track_step_parse_and_get(
573        #[case] input: &str,
574        #[case] expected_step: TrackStep,
575        #[case] expected_value: f64,
576    ) {
577        let (step, value) = TrackStep::parse_and_get(input).unwrap();
578        assert_eq!(step, expected_step);
579        assert_eq!(value, expected_value);
580    }
581
582    #[rstest]
583    #[case(TrackStep::One, 2.34, "2")]
584    #[case(TrackStep::PointOne, 2.34, "2.3")]
585    #[case(TrackStep::PointZeroOne, 2.345, "2.35")]
586    #[case(TrackStep::PointZeroZeroOne, 2.3456, "2.346")]
587    fn test_track_step_round_to_string(
588        #[case] step: TrackStep,
589        #[case] value: f64,
590        #[case] expected_str: &str,
591    ) {
592        let result_str = step.round_to_string(value);
593        assert_eq!(result_str, expected_str);
594    }
595}