Skip to main content

ass_editor/commands/event_commands/
timing.rs

1//! Command to adjust event timing by shifting start/end times.
2
3use super::helpers::{collect_event_lines, parse_event_line};
4use crate::commands::{CommandResult, EditorCommand};
5use crate::core::{EditorDocument, Position, Range, Result};
6use ass_core::parser::ast::EventType;
7use ass_core::utils::format_ass_time;
8
9#[cfg(not(feature = "std"))]
10use alloc::{
11    format,
12    string::{String, ToString},
13    vec::Vec,
14};
15
16/// Command to adjust event timing (shift start/end times)
17#[derive(Debug, Clone)]
18pub struct TimingAdjustCommand {
19    pub event_indices: Vec<usize>, // Events to adjust (empty = all events)
20    pub start_offset_cs: i32,      // Offset in centiseconds for start time
21    pub end_offset_cs: i32,        // Offset in centiseconds for end time
22    pub description: Option<String>,
23}
24
25impl TimingAdjustCommand {
26    /// Create a new timing adjustment command for specific events
27    pub fn new(event_indices: Vec<usize>, start_offset_cs: i32, end_offset_cs: i32) -> Self {
28        Self {
29            event_indices,
30            start_offset_cs,
31            end_offset_cs,
32            description: None,
33        }
34    }
35
36    /// Create a timing adjustment command for all events
37    pub fn all_events(start_offset_cs: i32, end_offset_cs: i32) -> Self {
38        Self {
39            event_indices: Vec::new(), // Empty means all events
40            start_offset_cs,
41            end_offset_cs,
42            description: None,
43        }
44    }
45
46    /// Adjust only start times (keep duration constant)
47    pub fn shift_start(event_indices: Vec<usize>, offset_cs: i32) -> Self {
48        Self::new(event_indices, offset_cs, offset_cs)
49    }
50
51    /// Adjust only end times (change duration)
52    pub fn shift_end(event_indices: Vec<usize>, offset_cs: i32) -> Self {
53        Self::new(event_indices, 0, offset_cs)
54    }
55
56    /// Scale duration (multiply by factor)
57    pub fn scale_duration(event_indices: Vec<usize>, factor: f64) -> Self {
58        // This is a simplified version - actual implementation would need to calculate per-event
59        let offset = (factor * 100.0) as i32 - 100; // Convert factor to centisecond offset
60        Self::new(event_indices, 0, offset)
61    }
62
63    /// Set a custom description for this command
64    #[must_use]
65    pub fn with_description(mut self, description: String) -> Self {
66        self.description = Some(description);
67        self
68    }
69}
70
71impl EditorCommand for TimingAdjustCommand {
72    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
73        let content = document.text();
74        let event_lines = collect_event_lines(&content)?;
75        let mut replacements = Vec::new();
76        let mut total_range: Option<Range> = None;
77
78        for event_line in event_lines {
79            let should_adjust =
80                self.event_indices.is_empty() || self.event_indices.contains(&event_line.index);
81
82            if !should_adjust {
83                continue;
84            }
85
86            if let Ok(event) = parse_event_line(event_line.line) {
87                if let (Ok(start_cs), Ok(end_cs)) = (event.start_time_cs(), event.end_time_cs()) {
88                    let new_start_cs = (start_cs as i32 + self.start_offset_cs).max(0) as u32;
89                    let new_end_cs = (end_cs as i32 + self.end_offset_cs).max(0) as u32;
90                    let final_end_cs = new_end_cs.max(new_start_cs + 1);
91
92                    let new_start_time = format_ass_time(new_start_cs);
93                    let new_end_time = format_ass_time(final_end_cs);
94
95                    let event_type_str = match event.event_type {
96                        EventType::Dialogue => "Dialogue",
97                        EventType::Comment => "Comment",
98                        _ => "Dialogue",
99                    };
100                    let new_line = format!(
101                        "{}: {},{},{},{},{},{},{},{},{},{}",
102                        event_type_str,
103                        event.layer,
104                        new_start_time,
105                        new_end_time,
106                        event.style,
107                        event.name,
108                        event.margin_l,
109                        event.margin_r,
110                        event.margin_v,
111                        event.effect,
112                        event.text
113                    );
114
115                    let change_range = Range::new(
116                        Position::new(event_line.start),
117                        Position::new(event_line.start + new_line.len()),
118                    );
119                    total_range = Some(match total_range {
120                        Some(existing) => existing.union(&change_range),
121                        None => change_range,
122                    });
123                    replacements.push((event_line.start, event_line.end, new_line));
124                }
125            }
126        }
127
128        for (start, end, new_line) in replacements.iter().rev() {
129            let range = Range::new(Position::new(*start), Position::new(*end));
130            document.replace(range, new_line)?;
131        }
132
133        let changes_made = replacements.len();
134
135        if changes_made > 0 {
136            Ok(CommandResult::success_with_change(
137                total_range.unwrap_or(Range::new(Position::new(0), Position::new(0))),
138                Position::new(document.len_bytes()),
139            )
140            .with_message(format!("Adjusted timing for {changes_made} events")))
141        } else {
142            Ok(CommandResult::success().with_message("No events were adjusted".to_string()))
143        }
144    }
145
146    fn description(&self) -> &str {
147        self.description.as_deref().unwrap_or("Adjust event timing")
148    }
149
150    fn memory_usage(&self) -> usize {
151        core::mem::size_of::<Self>()
152            + self.event_indices.len() * core::mem::size_of::<usize>()
153            + self.description.as_ref().map_or(0, |d| d.len())
154    }
155}