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), VideoOverlayPosition::TopCenter => format!("main_w*{}:main_h*{}", margin, margin), VideoOverlayPosition::Center => format!("main_w*{}:main_h*{}", margin, margin), }
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 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}