Skip to main content

rs_plugin_common_interfaces/video/
mod.rs

1use serde::{Deserialize, Serialize};
2use strum_macros::EnumString;
3
4use crate::{PluginCredential, RsRequest};
5
6#[cfg(feature = "rusqlite")]
7pub mod rusqlite;
8
9fn text_contains(text: &str, contains: &str) -> bool {
10    text.contains(&format!(".{}.", contains))
11        || text.starts_with(contains)
12        || text.ends_with(contains)
13}
14
15#[derive(
16    Debug,
17    Serialize,
18    Deserialize,
19    Clone,
20    PartialEq,
21    strum_macros::Display,
22    strum_macros::EnumString,
23    Default,
24)]
25#[serde(rename_all = "lowercase")]
26#[strum(serialize_all = "lowercase")]
27pub enum RsVideoFormat {
28    Mp4,
29    M4v,
30    Mov,
31    Mkv,
32    WebM,
33    Wmv,
34    Avi,
35    #[default]
36    Other,
37}
38
39impl RsVideoFormat {
40    pub fn from_filename(filename: &str) -> Self {
41        let filename = filename.to_lowercase();
42        if filename.ends_with(".mkv") {
43            Self::Mkv
44        } else if filename.ends_with(".mp4") {
45            Self::Mp4
46        } else if filename.ends_with(".m4v") {
47            Self::M4v
48        } else if filename.ends_with(".mov") {
49            Self::Mov
50        } else if filename.ends_with(".webm") {
51            Self::WebM
52        } else if filename.ends_with(".wmv") {
53            Self::Wmv
54        } else if filename.ends_with(".avi") {
55            Self::Avi
56        } else {
57            Self::Other
58        }
59    }
60
61    pub fn as_mime(&self) -> &str {
62        match self {
63            RsVideoFormat::Mp4 => "video/mp4",
64            RsVideoFormat::M4v => "video/x-m4v",
65            RsVideoFormat::Mov => "video/quicktime",
66            RsVideoFormat::Mkv => "application/x-matroska",
67            RsVideoFormat::WebM => "video/webm",
68            RsVideoFormat::Wmv => "video/x-ms-wmv",
69            RsVideoFormat::Avi => "video/x-msvideo",
70            RsVideoFormat::Other => "application/octet-stream",
71        }
72    }
73    pub fn from_mime(mime: &str) -> Option<Self> {
74        match mime {
75            "video/mp4" => Some(RsVideoFormat::Mp4),
76            "video/x-m4v" => Some(RsVideoFormat::M4v),
77            "video/quicktime" => Some(RsVideoFormat::Mov),
78            "application/x-matroska" => Some(RsVideoFormat::Mkv),
79            "video/webm" => Some(RsVideoFormat::WebM),
80            "video/x-ms-wmv" => Some(RsVideoFormat::Wmv),
81            "video/x-msvideo" => Some(RsVideoFormat::Avi),
82            "application/octet-stream" => Some(RsVideoFormat::Other),
83            _ => None,
84        }
85    }
86}
87
88#[derive(
89    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
90)]
91pub enum RsResolution {
92    #[strum(serialize = "4K")]
93    UHD,
94    #[strum(serialize = "1080p")]
95    FullHD,
96    #[strum(serialize = "720p")]
97    HD,
98    #[strum(default)]
99    Custom(String),
100    #[default]
101    Unknown,
102}
103
104impl RsResolution {
105    pub fn from_filename(filename: &str) -> Self {
106        let modified_filename = filename.replace([' ', '-', '_'], ".").to_lowercase();
107        if text_contains(&modified_filename, "1080p") {
108            RsResolution::FullHD
109        } else if text_contains(&modified_filename, "720p") {
110            RsResolution::HD
111        } else if text_contains(&modified_filename, "4k")
112            || text_contains(&modified_filename, "2160p")
113        {
114            RsResolution::UHD
115        } else {
116            RsResolution::Unknown
117        }
118    }
119}
120
121#[derive(
122    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
123)]
124pub enum RsVideoCodec {
125    H265,
126    H264,
127    AV1,
128    XVID,
129    #[strum(default)]
130    Custom(String),
131    #[default]
132    Unknown,
133}
134
135impl RsVideoCodec {
136    pub fn from_filename(filename: &str) -> Self {
137        let modified_filename = filename.replace([' ', '-', '_'], ".").to_lowercase();
138        if text_contains(&modified_filename, "x265")
139            || text_contains(&modified_filename, "x.265")
140            || text_contains(&modified_filename, "hevc")
141            || text_contains(&modified_filename, "h265")
142            || text_contains(&modified_filename, "h.265")
143        {
144            RsVideoCodec::H265
145        } else if text_contains(&modified_filename, "h264")
146            || text_contains(&modified_filename, "h.264")
147            || text_contains(&modified_filename, "x.264")
148            || text_contains(&modified_filename, "x264")
149        {
150            RsVideoCodec::H264
151        } else if text_contains(&modified_filename, "av1") {
152            RsVideoCodec::AV1
153        } else if text_contains(&modified_filename, "xvid") {
154            RsVideoCodec::XVID
155        } else {
156            RsVideoCodec::Unknown
157        }
158    }
159}
160
161#[derive(
162    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
163)]
164pub enum RsAudio {
165    #[strum(serialize = "Atmos")]
166    Atmos,
167    #[strum(serialize = "DDP5.1")]
168    DDP51,
169    #[strum(serialize = "DTSHD")]
170    DTSHD,
171    #[strum(serialize = "DTSX")]
172    DTSX,
173    #[strum(serialize = "DTS")]
174    DTS,
175    #[strum(serialize = "AC35.1")]
176    AC351,
177    #[strum(serialize = "AAC5.1")]
178    AAC51,
179    AAC,
180    MP3,
181    #[strum(default)]
182    Custom(String),
183    #[default]
184    Unknown,
185}
186
187impl RsAudio {
188    pub fn from_filename(filename: &str) -> Self {
189        let modified_filename = filename.replace([' ', '-', '_'], ".").to_lowercase();
190        if text_contains(&modified_filename, "atmos") {
191            RsAudio::Atmos
192        } else if text_contains(&modified_filename, "ddp5.1")
193            || text_contains(&modified_filename, "ddp51")
194            || text_contains(&modified_filename, "dolby.digital.plus.5.1")
195            || text_contains(&modified_filename, "dd51")
196        {
197            RsAudio::DDP51
198        } else if text_contains(&modified_filename, "dtshd") {
199            RsAudio::DTSHD
200        } else if text_contains(&modified_filename, "dtsx") {
201            RsAudio::DTSX
202        } else if text_contains(&modified_filename, "dts") {
203            RsAudio::DTS
204        } else if text_contains(&modified_filename, "ac35.1")
205            || text_contains(&modified_filename, "ac3.5.1")
206        {
207            RsAudio::AC351
208        } else if text_contains(&modified_filename, "aac5.1")
209            || text_contains(&modified_filename, "aac51")
210        {
211            RsAudio::AAC51
212        } else if text_contains(&modified_filename, "aac") {
213            RsAudio::AAC
214        } else if text_contains(&modified_filename, "mp3") {
215            RsAudio::MP3
216        } else {
217            RsAudio::Unknown
218        }
219    }
220
221    pub fn list_from_filename(filename: &str) -> Vec<Self> {
222        let mut result = vec![];
223        let modified_filename = filename.replace([' ', '-', '_'], ".").to_lowercase();
224        if text_contains(&modified_filename, "atmos") {
225            result.push(RsAudio::Atmos);
226        }
227        if text_contains(&modified_filename, "ddp5.1") {
228            result.push(RsAudio::DDP51);
229        }
230        if text_contains(&modified_filename, "dtshd") {
231            result.push(RsAudio::DTSHD);
232        }
233        if text_contains(&modified_filename, "dtsx") {
234            result.push(RsAudio::DTSX);
235        }
236        if text_contains(&modified_filename, "dts") {
237            result.push(RsAudio::DTS);
238        }
239        if text_contains(&modified_filename, "ac35.1")
240            || text_contains(&modified_filename, "ac3.5.1")
241        {
242            result.push(RsAudio::AC351);
243        }
244        result
245    }
246}
247
248#[derive(
249    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
250)]
251#[serde(rename_all = "camelCase")]
252#[strum(serialize_all = "camelCase")]
253pub enum VideoOverlayPosition {
254    TopLeft,
255    #[default]
256    TopRight,
257    BottomLeft,
258    BottomRight,
259    BottomCenter,
260    TopCenter,
261    Center,
262}
263
264#[derive(
265    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
266)]
267#[serde(rename_all = "camelCase")]
268#[strum(serialize_all = "camelCase")]
269pub enum VideoAlignment {
270    #[default]
271    Center,
272    Left,
273    Right,
274}
275
276#[derive(
277    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
278)]
279#[serde(rename_all = "camelCase")]
280#[strum(serialize_all = "camelCase")]
281pub enum VideoOverlayType {
282    #[default]
283    Watermark,
284
285    File,
286}
287
288impl VideoOverlayPosition {
289    pub fn as_filter(&self, margin: f64) -> String {
290        match self {
291            VideoOverlayPosition::TopLeft => format!("main_w*{}:main_h*{}", margin, margin),
292            VideoOverlayPosition::TopRight => format!("(main_w-w):min(main_h,main_w)*{}", margin),
293            VideoOverlayPosition::BottomLeft => format!("main_w*{}:(main_h-h)", margin),
294            VideoOverlayPosition::BottomRight => "(main_w-w):(main_h-h)".to_string(),
295            VideoOverlayPosition::BottomCenter => format!("main_w*{}:(main_h-h)", margin), //TODO
296            VideoOverlayPosition::TopCenter => format!("main_w*{}:main_h*{}", margin, margin), //TODO
297            VideoOverlayPosition::Center => format!("main_w*{}:main_h*{}", margin, margin), //TODO
298        }
299    }
300    pub fn as_ass_alignment(&self) -> String {
301        match self {
302            VideoOverlayPosition::TopLeft => String::from("7"),
303            VideoOverlayPosition::TopCenter => String::from("8"),
304            VideoOverlayPosition::TopRight => String::from("9"),
305            VideoOverlayPosition::Center => String::from("5"),
306            VideoOverlayPosition::BottomLeft => String::from("1"),
307            VideoOverlayPosition::BottomCenter => String::from("2"),
308            VideoOverlayPosition::BottomRight => String::from("3"),
309        }
310    }
311}
312
313#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
314pub struct VideoConvertInterval {
315    pub start: f64,
316    pub duration: Option<f64>,
317    /// will default to current input
318    pub input: Option<String>,
319}
320
321#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
322
323pub struct VideoOverlay {
324    #[serde(rename = "type")]
325    pub kind: VideoOverlayType,
326    pub path: String,
327    #[serde(default)]
328    pub position: VideoOverlayPosition,
329    pub margin: Option<f64>,
330    pub ratio: f32,
331    pub opacity: f32,
332}
333
334#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
335#[serde(rename_all = "camelCase")]
336pub struct VideoTextOverlay {
337    pub text: String,
338    pub font_color: Option<String>,
339    pub font: Option<String>,
340    #[serde(default)]
341    pub position: VideoOverlayPosition,
342    pub margin_vertical: Option<i32>,
343    pub margin_horizontal: Option<i32>,
344    pub margin_right: Option<i32>,
345    pub margin_bottom: Option<i32>,
346    pub font_size: i32,
347    pub opacity: Option<f32>,
348    pub shadow_color: Option<String>,
349    pub shado_opacity: Option<f32>,
350    pub start: Option<u32>,
351    pub end: Option<u32>,
352}
353
354#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
355#[serde(rename_all = "camelCase")]
356pub struct VideoConvertRequest {
357    pub id: String,
358    pub format: RsVideoFormat,
359    pub codec: Option<RsVideoCodec>,
360    pub crf: Option<u16>,
361    #[serde(default)]
362    pub no_audio: bool,
363    pub width: Option<String>,
364    pub height: Option<String>,
365    pub framerate: Option<u16>,
366    pub crop_width: Option<u16>,
367    pub crop_height: Option<u16>,
368    pub aspect_ratio: Option<String>,
369    pub aspect_ratio_alignment: Option<VideoAlignment>,
370    pub overlay: Option<VideoOverlay>,
371    pub texts: Option<Vec<VideoTextOverlay>>,
372    #[serde(default)]
373    pub intervals: Vec<VideoConvertInterval>,
374}
375
376#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
377#[serde(rename_all = "camelCase")]
378pub struct RsVideoTranscodeJob {
379    pub source: RsRequest,
380    pub request: VideoConvertRequest,
381}
382#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
383#[serde(rename_all = "camelCase")]
384pub struct RsVideoTranscodeJobPluginRequest {
385    pub job: RsVideoTranscodeJob,
386    pub credentials: PluginCredential,
387}
388
389#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
390#[serde(rename_all = "camelCase")]
391pub struct RsVideoTranscodeJobPluginAction {
392    pub job_id: String,
393    pub credentials: PluginCredential,
394}
395
396#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
397#[serde(rename_all = "camelCase")]
398pub struct RsVideoTranscodeJobStatus {
399    pub id: String,
400    pub status: RsVideoTranscodeStatus,
401    pub progress: f32,
402}
403
404#[derive(
405    Debug,
406    Serialize,
407    Deserialize,
408    Clone,
409    PartialEq,
410    strum_macros::Display,
411    strum_macros::EnumString,
412    Default,
413)]
414#[serde(rename_all = "lowercase")]
415#[strum(serialize_all = "lowercase")]
416pub enum RsVideoTranscodeStatus {
417    #[default]
418    Pending,
419    Downloading,
420    Queued,
421    Processing,
422    Completed,
423    Failed,
424    Canceled,
425}
426
427#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
428#[serde(rename_all = "camelCase")]
429pub struct RsVideoCapabilities {
430    pub video_codecs: Vec<RsVideoCodec>,
431    pub video_codecs_hw: Vec<RsVideoCodec>,
432    pub audio_codecs: Vec<RsAudio>,
433    pub video_formats: Vec<RsVideoFormat>,
434    pub max_duration: Option<u32>,
435    pub max_concurrent_jobs: Option<u16>,
436}
437
438#[derive(
439    Debug,
440    Serialize,
441    Deserialize,
442    Clone,
443    PartialEq,
444    strum_macros::Display,
445    strum_macros::EnumString,
446    Default,
447)]
448#[serde(rename_all = "lowercase")]
449#[strum(serialize_all = "lowercase")]
450pub enum RsVideoTranscodeCancelResponse {
451    #[default]
452    NotFound,
453    Cancelled,
454    Error,
455}