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