ass_editor/commands/event_commands/
timing.rs1use 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#[derive(Debug, Clone)]
18pub struct TimingAdjustCommand {
19 pub event_indices: Vec<usize>, pub start_offset_cs: i32, pub end_offset_cs: i32, pub description: Option<String>,
23}
24
25impl TimingAdjustCommand {
26 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 pub fn all_events(start_offset_cs: i32, end_offset_cs: i32) -> Self {
38 Self {
39 event_indices: Vec::new(), start_offset_cs,
41 end_offset_cs,
42 description: None,
43 }
44 }
45
46 pub fn shift_start(event_indices: Vec<usize>, offset_cs: i32) -> Self {
48 Self::new(event_indices, offset_cs, offset_cs)
49 }
50
51 pub fn shift_end(event_indices: Vec<usize>, offset_cs: i32) -> Self {
53 Self::new(event_indices, 0, offset_cs)
54 }
55
56 pub fn scale_duration(event_indices: Vec<usize>, factor: f64) -> Self {
58 let offset = (factor * 100.0) as i32 - 100; Self::new(event_indices, 0, offset)
61 }
62
63 #[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}