1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use std::{
    borrow::Cow,
    fmt::Display,
    fs::File,
    io::{BufWriter, Write},
    path::Path,
    str::FromStr,
};

use encoding_rs::Encoding;

use crate::{errors::Error, Moment, TimeDelta};

/// Base trait for all subtitle implementations.
pub trait Subtitle: Display + FromStr {
    /// Event type for the given subtitle format
    type Event;

    /// Load subtitle from given path.
    /// Automatically attempts to detect the encoding to use from the file contents.
    ///
    /// # Errors
    ///
    /// If an error is encountered while opening the file, returns [`Error::FileIoError`]
    fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
        Self::from_path_with_encoding(path, None)
    }

    /// Load subtitle format from path using the given encoding
    ///
    /// # Errors
    ///
    /// If an error is encountered while opening the file, returns [`Error::FileIoError`]
    fn from_path_with_encoding(
        path: impl AsRef<Path>,
        encoding: Option<&'static Encoding>,
    ) -> Result<Self, Error>;

    /// Get list of events as a slice
    fn events(&self) -> &[Self::Event];

    /// Get list of events as a mutable slice
    fn events_mut(&mut self) -> &mut [Self::Event];

    /// Try to get event at given index
    fn event(&self, index: usize) -> Option<&Self::Event> {
        self.events().get(index)
    }
    /// Try to get mutable event at given index
    fn event_mut(&mut self, index: usize) -> Option<&mut Self::Event> {
        self.events_mut().get_mut(index)
    }

    /// Write subtitles to file at the given path
    ///
    /// # Errors
    ///
    /// Returns [`Error::FileIoError`] if method fails to create file at the specified path
    fn export(&self, path: impl AsRef<Path>) -> Result<(), Error> {
        let file = File::create(path)?;
        let mut writer = BufWriter::new(file);

        Ok(write!(writer, "{self}")?)
    }
}

/// Trait representing textual subtitle formats
pub trait TextSubtitle: Subtitle
where
    Self::Event: TextEvent,
{
    /// Remove all styling/formatting information from the text and subtitle metadata
    fn strip_formatting(&mut self) {
        for event in self.events_mut() {
            event.strip_formatting();
        }
    }
}

/// Time-based subtitle
pub trait TimedSubtitle: Subtitle
where
    Self::Event: TimedEvent,
{
    /// Shift all events in subtitle by given amount of time, in milliseconds.
    fn shift(&mut self, delta: TimeDelta)
    where
        <Self as Subtitle>::Event: TimedEvent,
    {
        for event in self.events_mut() {
            event.shift(delta);
        }
    }
}

/// Trait offering helper functions for textual subtitle events
pub trait TextEvent: TextEventInterface {
    /// Remove all formatting tags from event text
    fn strip_formatting(&mut self) {
        self.set_text(self.unformatted_text().into_owned());
    }

    /// Get text content with all formatting tags removed
    fn unformatted_text(&self) -> Cow<'_, String>;

    /// Get text in plaintext, without formatting and with no character escapes.
    ///
    /// As an example, this differs from `unformatted_text()` for SubStation files,
    /// where `\N` is converted to an actual newline character, `\n`.
    fn as_plaintext(&self) -> Cow<'_, String> {
        self.unformatted_text()
    }
}

/// Interface for getting/modifying textual subtitle event fields.
/// Required for implementation of [`TextEvent`].
///
/// Not recommended to use this trait outside of the implementation of [`TextEvent`],
/// as it will pollute the namespace of subtitle event type properties.
pub trait TextEventInterface {
    /// Text associated with event
    fn text(&self) -> String;

    /// Modify text associated with event
    fn set_text(&mut self, text: String);
}

/// Helper methods for time-based subtitle events.
pub trait TimedEvent: TimedEventInterface {
    /// Shift subtitle event by given amount of time in milliseconds.
    ///
    /// Positive numbers will result in the subtitle event appearing later,
    /// while negative numbers will make the event appear earlier.
    fn shift(&mut self, delta: TimeDelta) {
        self.set_start(self.start() + delta);
        self.set_end(self.end() + delta);
    }

    /// Get duration of event in milliseconds, as a `TimeDelta`
    fn duration(&self) -> TimeDelta {
        self.end() - self.start()
    }
}

/// Interface for interacting with timed events.
/// Required for implementation of [`TimedEvent`].
pub trait TimedEventInterface {
    /// Start time of event
    fn start(&self) -> Moment;

    /// End time of event
    fn end(&self) -> Moment;

    /// Modify start time of event
    fn set_start(&mut self, moment: Moment);

    /// Modify end time of event
    fn set_end(&mut self, moment: Moment);
}