Skip to main content

ass_editor/commands/karaoke_commands/
adjust_impl.rs

1//! Execution and timing-rewrite logic for [`AdjustKaraokeCommand`].
2
3use super::{AdjustKaraokeCommand, TimingAdjustment};
4use crate::commands::{CommandResult, EditorCommand};
5use crate::core::{EditorDocument, Position, Range, Result};
6
7#[cfg(not(feature = "std"))]
8use alloc::{
9    format,
10    string::{String, ToString},
11    vec::Vec,
12};
13
14impl EditorCommand for AdjustKaraokeCommand {
15    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
16        let original_text = document.text_range(self.range)?;
17        let adjusted_text = self.adjust_karaoke_timing(&original_text)?;
18
19        document.replace_raw(self.range, &adjusted_text)?;
20
21        let end_pos = Position::new(self.range.start.offset + adjusted_text.len());
22        let range = Range::new(self.range.start, end_pos);
23
24        Ok(CommandResult::success_with_change(range, end_pos))
25    }
26
27    fn description(&self) -> &str {
28        match self.adjustment {
29            TimingAdjustment::Scale(_) => "Scale karaoke timing",
30            TimingAdjustment::Offset(_) => "Offset karaoke timing",
31            TimingAdjustment::SetAll(_) => "Set karaoke timing",
32            TimingAdjustment::Custom(_) => "Apply custom karaoke timing",
33        }
34    }
35
36    fn memory_usage(&self) -> usize {
37        let adjustment_size = match &self.adjustment {
38            TimingAdjustment::Custom(vec) => vec.len() * core::mem::size_of::<u32>(),
39            _ => 0,
40        };
41        core::mem::size_of::<Self>() + adjustment_size
42    }
43}
44
45impl AdjustKaraokeCommand {
46    /// Adjust karaoke timing in text using ass-core's ExtensionRegistry system
47    fn adjust_karaoke_timing(&self, text: &str) -> Result<String> {
48        use ass_core::analysis::events::tags::parse_override_block_with_registry;
49        use ass_core::plugin::{tags::karaoke::create_karaoke_handlers, ExtensionRegistry};
50
51        // Create registry with karaoke handlers
52        let mut registry = ExtensionRegistry::new();
53        for handler in create_karaoke_handlers() {
54            registry.register_tag_handler(handler).map_err(|e| {
55                crate::core::errors::EditorError::ValidationError {
56                    message: format!("Failed to register karaoke handler: {e:?}"),
57                }
58            })?;
59        }
60
61        let mut result = String::new();
62        let mut chars = text.chars().peekable();
63        let mut custom_index = 0;
64
65        while let Some(ch) = chars.next() {
66            if ch == '{' {
67                // Found override block - extract content
68                let mut override_content = String::new();
69                let mut brace_count = 1;
70
71                for inner_ch in chars.by_ref() {
72                    if inner_ch == '{' {
73                        brace_count += 1;
74                    } else if inner_ch == '}' {
75                        brace_count -= 1;
76                        if brace_count == 0 {
77                            break;
78                        }
79                    }
80                    override_content.push(inner_ch);
81                }
82
83                // Use ass-core's registry-based parser
84                let mut tags = Vec::new();
85                let mut diagnostics = Vec::new();
86                parse_override_block_with_registry(
87                    &override_content,
88                    0,
89                    &mut tags,
90                    &mut diagnostics,
91                    Some(&registry),
92                );
93
94                // Process karaoke tags using ass-core's validated data
95                let processed_content = self.adjust_karaoke_tags_with_registry(
96                    &override_content,
97                    &tags,
98                    &mut custom_index,
99                )?;
100
101                result.push('{');
102                result.push_str(&processed_content);
103                result.push('}');
104            } else {
105                result.push(ch);
106            }
107        }
108
109        Ok(result)
110    }
111
112    /// Adjust karaoke tags using registry-validated tag information
113    fn adjust_karaoke_tags_with_registry(
114        &self,
115        original_content: &str,
116        tags: &[ass_core::analysis::events::tags::OverrideTag],
117        custom_index: &mut usize,
118    ) -> Result<String> {
119        let mut result = original_content.to_string();
120
121        // Process tags in reverse order to maintain position accuracy
122        for tag in tags.iter().rev() {
123            if tag.name().starts_with('k') {
124                // This tag was validated by ass-core's karaoke handlers
125                let tag_name = tag.name();
126                let args = tag.args();
127
128                // Extract duration from args (ass-core already validated this)
129                let current_duration: u32 = args.trim().parse().unwrap_or(0);
130
131                // Calculate new duration based on adjustment type
132                let new_duration = match &self.adjustment {
133                    TimingAdjustment::Scale(factor) => {
134                        ((current_duration as f32 * factor) as u32).max(1)
135                    }
136                    TimingAdjustment::Offset(offset) => {
137                        ((current_duration as i32 + offset).max(1)) as u32
138                    }
139                    TimingAdjustment::SetAll(duration) => *duration,
140                    TimingAdjustment::Custom(timings) => {
141                        if *custom_index < timings.len() {
142                            let timing = timings[*custom_index];
143                            *custom_index += 1;
144                            timing
145                        } else {
146                            current_duration
147                        }
148                    }
149                };
150
151                // Replace the validated tag with adjusted version
152                let old_tag = format!("\\{tag_name}{current_duration}");
153                let new_tag = format!("\\{tag_name}{new_duration}");
154                result = result.replace(&old_tag, &new_tag);
155            }
156        }
157
158        Ok(result)
159    }
160}