sreepathi-ravikumar commited on
Commit
11113c7
·
verified ·
1 Parent(s): 1f2ab31

Upload 2 files

Browse files
rust_combiner/Cargo.toml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "rust_combiner"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ name = "rust_combiner"
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ pyo3 = { version = "0.21", features = ["extension-module"] }
12
+ uuid = { version = "1.7", features = ["v4"] }
rust_combiner/src/lib.rs ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use pyo3::prelude::*;
2
+ use std::process::Command;
3
+ use std::path::Path;
4
+
5
+ #[pyfunction]
6
+ fn render_video(
7
+ id: usize,
8
+ image_path: String,
9
+ audio_path: String,
10
+ duration: f64,
11
+ words: Vec<(String, (u32, u32, u32, u32))>,
12
+ output_dir: String,
13
+ ) -> PyResult<String> {
14
+ // Validate paths
15
+ if !Path::new(&image_path).exists() {
16
+ return Err(pyo3::exceptions::PyFileNotFoundError::new_err(format!(
17
+ "Image not found: {}",
18
+ image_path
19
+ )));
20
+ }
21
+ if !Path::new(&audio_path).exists() {
22
+ return Err(pyo3::exceptions::PyFileNotFoundError::new_err(format!(
23
+ "Audio not found: {}",
24
+ audio_path
25
+ )));
26
+ }
27
+
28
+ let output_path = format!("{}/clip{}.mp4", output_dir, id);
29
+ let duration_str = duration.to_string();
30
+ let n_words = words.len() as f64;
31
+ let highlight_duration = duration / n_words;
32
+
33
+ // Build complex filter graph
34
+ let mut filter_complex = String::new();
35
+ let mut last_output = "0:v".to_string();
36
+
37
+ for (i, (_, (x, y, w, h))) in words.iter().enumerate() {
38
+ let start = i as f64 * highlight_duration;
39
+ let end = start + highlight_duration;
40
+
41
+ filter_complex.push_str(&format!(
42
+ "[{last_out}]drawbox=x={x}:y={y}:w={w}:h={h}:[email protected]:t=fill:enable='between(t,{start:.3},{end:.3})'[v{i}];",
43
+ last_out = last_output,
44
+ x = x,
45
+ y = y,
46
+ w = w,
47
+ h = h,
48
+ start = start,
49
+ end = end,
50
+ i = i
51
+ ));
52
+ last_output = format!("[v{i}]");
53
+ }
54
+
55
+ // Remove trailing semicolon
56
+ filter_complex.pop();
57
+
58
+ // FFmpeg command
59
+ let status = Command::new("ffmpeg")
60
+ .args(&[
61
+ "-y",
62
+ "-hwaccel", "auto",
63
+ "-loop", "1",
64
+ "-i", &image_path,
65
+ "-i", &audio_path,
66
+ "-filter_complex", &filter_complex,
67
+ "-map", &last_output,
68
+ "-map", "1:a",
69
+ "-c:v", "libx264",
70
+ "-preset", "fast",
71
+ "-crf", "18",
72
+ "-pix_fmt", "yuv420p",
73
+ "-movflags", "+faststart",
74
+ "-c:a", "aac",
75
+ "-b:a", "192k",
76
+ "-r", "60",
77
+ "-t", &duration_str,
78
+ "-vsync", "2",
79
+ &output_path,
80
+ ])
81
+ .status();
82
+
83
+ match status {
84
+ Ok(exit_status) if exit_status.success() => Ok(output_path),
85
+ Ok(_) => Err(pyo3::exceptions::PyRuntimeError::new_err("FFmpeg command failed")),
86
+ Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
87
+ "Failed to execute FFmpeg: {}",
88
+ e
89
+ ))),
90
+ }
91
+ }
92
+
93
+ #[pymodule]
94
+ fn rust_highlight(_py: Python, m: &PyModule) -> PyResult<()> {
95
+ m.add_function(wrap_pyfunction!(render_video, m)?)?;
96
+ Ok(())
97
+ }