Function bodies 405 total
default_theme function · rust · L36-L39 (4 LOC)src-tauri/src/services/settings.rs
fn default_theme() -> ThemeMode {
ThemeMode::Dark
}get_settings_path function · rust · L40-L48 (9 LOC)src-tauri/src/services/settings.rs
fn get_settings_path() -> Result<PathBuf, String> {
let app_dir = dirs::data_local_dir()
.ok_or("Failed to get app data directory")?;
let settings_dir = app_dir.join("cutclean");
fs::create_dir_all(&settings_dir)
.map_err(|e| format!("Failed to create settings directory: {}", e))?;
Ok(settings_dir.join(SETTINGS_FILE))
}get_settings function · rust · L49-L61 (13 LOC)src-tauri/src/services/settings.rs
pub fn get_settings() -> AppSettings {
get_settings_path()
.and_then(|path| {
fs::read_to_string(&path)
.map_err(|e| format!("Failed to read settings: {}", e))
})
.and_then(|json| {
serde_json::from_str(&json)
.map_err(|e| format!("Failed to parse settings: {}", e))
})
.unwrap_or_default()
}set_theme function · rust · L62-L72 (11 LOC)src-tauri/src/services/settings.rs
pub fn set_theme(mode: ThemeMode) -> Result<(), String> {
let mut settings = get_settings();
settings.theme = mode;
let path = get_settings_path()?;
let json = serde_json::to_string_pretty(&settings)
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
fs::write(&path, json)
.map_err(|e| format!("Failed to write settings: {}", e))?;
Ok(())
}model_name function · rust · L39-L47 (9 LOC)src-tauri/src/services/transcription.rs
pub fn model_name(&self) -> &str {
match self {
WhisperModel::Tiny => "tiny",
WhisperModel::Base => "base",
WhisperModel::Small => "small",
WhisperModel::Medium => "medium",
WhisperModel::Large => "large",
}
}file_name function · rust · L50-L58 (9 LOC)src-tauri/src/services/transcription.rs
pub fn file_name(&self) -> &str {
match self {
WhisperModel::Tiny => "ggml-tiny.bin",
WhisperModel::Base => "ggml-base.bin",
WhisperModel::Small => "ggml-small.bin",
WhisperModel::Medium => "ggml-medium.bin",
WhisperModel::Large => "ggml-large.bin",
}
}size_bytes function · rust · L61-L69 (9 LOC)src-tauri/src/services/transcription.rs
pub fn size_bytes(&self) -> u64 {
match self {
WhisperModel::Tiny => 74 * 1024 * 1024,
WhisperModel::Base => 142 * 1024 * 1024,
WhisperModel::Small => 466 * 1024 * 1024,
WhisperModel::Medium => 1500 * 1024 * 1024,
WhisperModel::Large => 2900 * 1024 * 1024,
}
}Repobility · open methodology · https://repobility.com/research/
display_name function · rust · L80-L88 (9 LOC)src-tauri/src/services/transcription.rs
pub fn display_name(&self) -> &str {
match self {
WhisperModel::Tiny => "Tiny (74MB)",
WhisperModel::Base => "Base (142MB)",
WhisperModel::Small => "Small (466MB)",
WhisperModel::Medium => "Medium (1.5GB)",
WhisperModel::Large => "Large (2.9GB)",
}
}from_str function · rust · L91-L100 (10 LOC)src-tauri/src/services/transcription.rs
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"tiny" => Some(WhisperModel::Tiny),
"base" => Some(WhisperModel::Base),
"small" => Some(WhisperModel::Small),
"medium" => Some(WhisperModel::Medium),
"large" => Some(WhisperModel::Large),
_ => None,
}
}default function · rust · L104-L106 (3 LOC)src-tauri/src/services/transcription.rs
fn default() -> Self {
WhisperModel::Tiny // Use Tiny by default for 3-4x faster transcription
}default function · rust · L186-L193 (8 LOC)src-tauri/src/services/transcription.rs
fn default() -> Self {
Self {
model: WhisperModel::Tiny, // Use Tiny for 3-4x faster transcription
language: Some("en".to_string()), // Force English to skip language detection
word_timestamps: true,
threads: 0, // 0 = auto (will be optimized based on audio length)
}
}get_models_dir function · rust · L197-L210 (14 LOC)src-tauri/src/services/transcription.rs
pub fn get_models_dir() -> Result<PathBuf, String> {
let app_dir = dirs::data_local_dir()
.ok_or("Failed to get app data directory")?;
let models_dir = app_dir
.join("cutclean")
.join("whisper-models");
// Create directory if it doesn't exist
std::fs::create_dir_all(&models_dir)
.map_err(|e| format!("Failed to create models directory: {}", e))?;
Ok(models_dir)
}get_model_path function · rust · L213-L216 (4 LOC)src-tauri/src/services/transcription.rs
pub fn get_model_path(model: WhisperModel) -> Result<PathBuf, String> {
let models_dir = get_models_dir()?;
Ok(models_dir.join(model.file_name()))
}is_model_downloaded function · rust · L219-L224 (6 LOC)src-tauri/src/services/transcription.rs
pub fn is_model_downloaded(model: WhisperModel) -> bool {
match get_model_path(model) {
Ok(path) => path.exists(),
Err(_) => false,
}
}download_model function · rust · L227-L327 (101 LOC)src-tauri/src/services/transcription.rs
pub fn download_model(
model: WhisperModel,
app: Option<&AppHandle>,
) -> Result<PathBuf, String> {
let model_path = get_model_path(model)?;
// Check if already downloaded
if model_path.exists() {
info!("Model already exists at: {:?}", model_path);
return Ok(model_path);
}
let url = model.download_url();
info!("Downloading model from: {}", url);
let total_size = model.size_bytes();
// Create parent directories
let models_dir = model_path
.parent()
.ok_or("Failed to get parent directory")?;
std::fs::create_dir_all(models_dir)
.map_err(|e| format!("Failed to create models directory: {}", e))?;
// Download using ureq
let response = ureq::get(&url)
.timeout(Duration::from_secs(600))
.call()
.map_err(|e| format!("Failed to download model: {}", e))?;
// Create temporary file
let temp_path = model_path.with_extension("tmp");
let file = File::create(&temp_paRepobility analyzer · published findings · https://repobility.com
get_available_models function · rust · L330-L339 (10 LOC)src-tauri/src/services/transcription.rs
pub fn get_available_models() -> Vec<ModelInfo> {
let models_dir = get_models_dir().unwrap_or_else(|_| PathBuf::from("."));
vec![
model_info(WhisperModel::Tiny, &models_dir),
model_info(WhisperModel::Base, &models_dir),
model_info(WhisperModel::Small, &models_dir),
model_info(WhisperModel::Medium, &models_dir),
model_info(WhisperModel::Large, &models_dir),
]
}model_info function · rust · L351-L364 (14 LOC)src-tauri/src/services/transcription.rs
fn model_info(model: WhisperModel, models_dir: &PathBuf) -> ModelInfo {
let model_path = models_dir.join(model.file_name());
let is_downloaded = model_path.exists();
ModelInfo {
model_type: format!("{:?}", model).to_lowercase(),
file_name: model.file_name().to_string(),
display_name: model.display_name().to_string(),
size_bytes: model.size_bytes(),
is_downloaded,
path: is_downloaded.then(|| model_path.to_string_lossy().to_string()),
}
}wav_to_f32_samples function · rust · L367-L388 (22 LOC)src-tauri/src/services/transcription.rs
fn wav_to_f32_samples(wav_data: &[u8]) -> Result<Vec<f32>, String> {
// Validate WAV header
if wav_data.len() < 44 || &wav_data[0..4] != b"RIFF" {
return Err("Invalid WAV file".to_string());
}
// Extract audio format info
let channels = u16::from_le_bytes([wav_data[22], wav_data[23]]) as usize;
let bits_per_sample = u16::from_le_bytes([wav_data[34], wav_data[35]]) as usize;
// Find data chunk
let data_start = wav_data[12..]
.windows(4)
.position(|w| w == b"data")
.map(|p| 12 + p + 8)
.ok_or_else(|| "No data chunk found".to_string())?;
let audio_data = &wav_data[data_start..];
// Convert PCM to f32
convert_pcm_to_f32(audio_data, bits_per_sample, channels)
}convert_pcm_to_f32 function · rust · L391-L463 (73 LOC)src-tauri/src/services/transcription.rs
fn convert_pcm_to_f32(
audio_data: &[u8],
bits_per_sample: usize,
channels: usize,
) -> Result<Vec<f32>, String> {
let samples: Vec<f32> = match bits_per_sample {
16 => {
let num_samples = audio_data.len() / 2;
let mut result = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let sample = i16::from_le_bytes([
audio_data[i * 2],
audio_data[i * 2 + 1],
]);
result.push(sample as f32 / i16::MAX as f32);
}
result
}
24 => {
let num_samples = audio_data.len() / 3;
let mut result = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let byte1 = audio_data[i * 3] as i32;
let byte2 = audio_data[i * 3 + 1] as i32;
let byte3 = audio_data[i * 3 + 2] as i32;
let sample = if byte3 & 0x80 != 0 {
new function · rust · L471-L475 (5 LOC)src-tauri/src/services/transcription.rs
pub fn new(context: WhisperContext) -> Self {
Self {
context: Arc::new(context),
}
}inner function · rust · L476-L479 (4 LOC)src-tauri/src/services/transcription.rs
pub fn inner(&self) -> &WhisperContext {
&self.context
}clone function · rust · L480-L485 (6 LOC)src-tauri/src/services/transcription.rs
pub fn clone(&self) -> Self {
Self {
context: Arc::clone(&self.context),
}
}get_whisper_context function · rust · L493-L532 (40 LOC)src-tauri/src/services/transcription.rs
fn get_whisper_context(model: WhisperModel) -> Result<ContextWrapper, String> {
unsafe {
// Check if we need to load a different model
if let Some(last_model) = LAST_MODEL {
if last_model == model && WHISPER_CONTEXT.is_some() {
return Ok(WHISPER_CONTEXT.as_ref().unwrap().clone());
}
}
// Load new model
let model_path = get_model_path(model)?;
if !model_path.exists() {
return Err(format!(
"Model not found: {}. Please download it first.",
model_path.display()
));
}
info!("Loading Whisper model from: {:?}", model_path);
let params = WhisperContextParameters {
use_gpu: false, // CPU-only for compatibility
..Default::default()
};
let context = WhisperContext::new_with_params(
&model_path.to_string_lossy(),
params,
).map_err(|e| format!("Failed toIf a scraper extracted this row, it came from Repobility (https://repobility.com)
transcribe_with_whisper function · rust · L535-L673 (139 LOC)src-tauri/src/services/transcription.rs
fn transcribe_with_whisper(
audio_data: &[f32],
config: &TranscriptionConfig,
) -> Result<TranscriptionInternalResult, String> {
let start_time = Instant::now();
info!("Starting transcription with {} samples", audio_data.len());
// Get Whisper context
let context_wrapper = get_whisper_context(config.model)?;
let context = context_wrapper.inner();
// Configure parameters for word-level timestamps
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
// Set language
if let Some(ref lang) = config.language {
params.set_language(Some(lang));
} else {
params.set_language(None); // Auto-detect
}
params.set_translate(false);
// Optimize threads for short recordings - fewer threads reduces overhead
// Whisper's native sample rate
const WHISPER_SAMPLE_RATE: usize = 16000;
let audio_duration_sec = audio_data.len() as f64 / WHISPER_SAMPLE_RATE as f64;
let optimized_threads = if language_id_to_str function · rust · L683-L734 (52 LOC)src-tauri/src/services/transcription.rs
fn language_id_to_str(id: i32) -> Option<String> {
// whisper-rs language IDs
match id {
0 => Some("en".to_string()),
1 => Some("zh".to_string()),
2 => Some("de".to_string()),
3 => Some("es".to_string()),
4 => Some("ru".to_string()),
5 => Some("ko".to_string()),
6 => Some("fr".to_string()),
7 => Some("ja".to_string()),
8 => Some("pt".to_string()),
9 => Some("tr".to_string()),
10 => Some("pl".to_string()),
11 => Some("ca".to_string()),
12 => Some("nl".to_string()),
13 => Some("ar".to_string()),
14 => Some("sv".to_string()),
15 => Some("it".to_string()),
16 => Some("id".to_string()),
17 => Some("hi".to_string()),
18 => Some("fi".to_string()),
19 => Some("vi".to_string()),
20 => Some("he".to_string()),
21 => Some("uk".to_string()),
22 => Some("el".to_string()),
23 => Some("ms".to_string()),
transcribe_audio function · rust · L737-L780 (44 LOC)src-tauri/src/services/transcription.rs
pub fn transcribe_audio(
audio_path: &str,
clip_id: &str,
config: &TranscriptionConfig,
) -> Result<TranscriptionResult, String> {
let start_time = Instant::now();
// Check if audio file exists
if !Path::new(audio_path).exists() {
return Err(format!("Audio file not found: {}", audio_path));
}
// Read WAV file
let wav_data = std::fs::read(audio_path)
.map_err(|e| format!("Failed to read audio file: {}", e))?;
// Convert to f32 samples
let samples = wav_to_f32_samples(&wav_data)?;
if samples.is_empty() {
return Ok(TranscriptionResult {
clip_id: clip_id.to_string(),
words: vec![],
full_text: String::new(),
language: "en".to_string(),
processing_time: start_time.elapsed().as_secs_f64(),
});
}
info!("Transcribing {} samples ({} seconds)", samples.len(), samples.len() / 16000);
// Run transcription
let internal_result = transcribe_wtranscribe_video_async function · rust · L786-L881 (96 LOC)src-tauri/src/services/transcription.rs
pub fn transcribe_video_async(
video_path: String,
clip_id: String,
app: AppHandle,
) -> Result<(), String> {
use std::thread;
let clip_id_clone = clip_id.clone();
thread::spawn(move || {
// Emit initial progress
let _ = app.emit("transcription-progress", TranscriptionProgress {
clip_id: clip_id_clone.clone(),
progress: 0,
stage: "Initializing".to_string(),
});
// Extract audio
let _ = app.emit("transcription-progress", TranscriptionProgress {
clip_id: clip_id_clone.clone(),
progress: 10,
stage: "Extracting audio".to_string(),
});
let wav_path = match crate::services::audio_extractor::extract_audio_to_temp(&video_path) {
Ok(path) => path,
Err(e) => {
let _ = app.emit("transcription-error", TranscriptionError {
clip_id: clip_id_clone.clone(),
error: formatest_whisper_model_names function · rust · L888-L894 (7 LOC)src-tauri/src/services/transcription.rs
fn test_whisper_model_names() {
assert_eq!(WhisperModel::Tiny.file_name(), "ggml-tiny.bin");
assert_eq!(WhisperModel::Base.file_name(), "ggml-base.bin");
assert_eq!(WhisperModel::Small.file_name(), "ggml-small.bin");
assert_eq!(WhisperModel::Medium.file_name(), "ggml-medium.bin");
assert_eq!(WhisperModel::Large.file_name(), "ggml-large.bin");
}test_whisper_model_sizes function · rust · L897-L902 (6 LOC)src-tauri/src/services/transcription.rs
fn test_whisper_model_sizes() {
assert!(WhisperModel::Tiny.size_bytes() < WhisperModel::Base.size_bytes());
assert!(WhisperModel::Base.size_bytes() < WhisperModel::Small.size_bytes());
assert!(WhisperModel::Small.size_bytes() < WhisperModel::Medium.size_bytes());
assert!(WhisperModel::Medium.size_bytes() < WhisperModel::Large.size_bytes());
}test_whisper_model_from_str function · rust · L905-L909 (5 LOC)src-tauri/src/services/transcription.rs
fn test_whisper_model_from_str() {
assert_eq!(WhisperModel::from_str("tiny"), Some(WhisperModel::Tiny));
assert_eq!(WhisperModel::from_str("SMALL"), Some(WhisperModel::Small));
assert_eq!(WhisperModel::from_str("invalid"), None);
}test_default_config function · rust · L912-L918 (7 LOC)src-tauri/src/services/transcription.rs
fn test_default_config() {
let config = TranscriptionConfig::default();
assert_eq!(config.model, WhisperModel::Small);
assert_eq!(config.language, Some("en".to_string()));
assert!(config.word_timestamps);
assert_eq!(config.threads, 0);
}All rows above produced by Repobility · https://repobility.com
test_transcribed_word_structure function · rust · L921-L934 (14 LOC)src-tauri/src/services/transcription.rs
fn test_transcribed_word_structure() {
let word = TranscribedWord {
id: "test_id".to_string(),
word: "hello".to_string(),
start_time: 0.0,
end_time: 0.5,
confidence: 0.95,
};
assert_eq!(word.word, "hello");
assert_eq!(word.start_time, 0.0);
assert_eq!(word.end_time, 0.5);
assert_eq!(word.confidence, 0.95);
}test_transcription_result_structure function · rust · L937-L959 (23 LOC)src-tauri/src/services/transcription.rs
fn test_transcription_result_structure() {
let result = TranscriptionResult {
clip_id: "clip1".to_string(),
words: vec![
TranscribedWord {
id: "1".to_string(),
word: "hello".to_string(),
start_time: 0.0,
end_time: 0.5,
confidence: 0.95,
},
],
full_text: "hello".to_string(),
language: "en".to_string(),
processing_time: 1.5,
};
assert_eq!(result.clip_id, "clip1");
assert_eq!(result.words.len(), 1);
assert_eq!(result.full_text, "hello");
assert_eq!(result.language, "en");
assert_eq!(result.processing_time, 1.5);
}test_transcription_progress_structure function · rust · L962-L972 (11 LOC)src-tauri/src/services/transcription.rs
fn test_transcription_progress_structure() {
let progress = TranscriptionProgress {
clip_id: "clip1".to_string(),
progress: 50,
stage: "Processing".to_string(),
};
assert_eq!(progress.clip_id, "clip1");
assert_eq!(progress.progress, 50);
assert_eq!(progress.stage, "Processing");
}test_transcription_serialization function · rust · L975-L989 (15 LOC)src-tauri/src/services/transcription.rs
fn test_transcription_serialization() {
let result = TranscriptionResult {
clip_id: "clip1".to_string(),
words: vec![],
full_text: "test".to_string(),
language: "en".to_string(),
processing_time: 1.0,
};
let json = serde_json::to_string(&result);
assert!(json.is_ok());
let parsed: Result<TranscriptionResult, _> = serde_json::from_str(&json.unwrap());
assert!(parsed.is_ok());
}test_word_serialization function · rust · L992-L1008 (17 LOC)src-tauri/src/services/transcription.rs
fn test_word_serialization() {
let word = TranscribedWord {
id: "1".to_string(),
word: "hello".to_string(),
start_time: 0.0,
end_time: 0.5,
confidence: 0.95,
};
let json = serde_json::to_string(&word);
assert!(json.is_ok());
let parsed: Result<TranscribedWord, _> = serde_json::from_str(&json.unwrap());
assert!(parsed.is_ok());
let parsed_word = parsed.unwrap();
assert_eq!(parsed_word.word, "hello");
}test_model_info_structure function · rust · L1011-L1019 (9 LOC)src-tauri/src/services/transcription.rs
fn test_model_info_structure() {
let models_dir = PathBuf::from("/tmp/models");
let info = model_info(WhisperModel::Tiny, &models_dir);
assert_eq!(info.model_type, "tiny");
assert_eq!(info.file_name, "ggml-tiny.bin");
assert_eq!(info.display_name, "Tiny (74MB)");
assert_eq!(info.size_bytes, 74 * 1024 * 1024);
}get_video_metadata function · rust · L22-L134 (113 LOC)src-tauri/src/services/video_processor.rs
pub fn get_video_metadata(video_path: &str) -> Result<VideoMetadata, String> {
if !Path::new(video_path).exists() {
return Err(format!("Video file not found: {}", video_path));
}
// Use ffprobe to get JSON metadata
let output = Command::new("ffprobe")
.args([
"-v", "quiet",
"-print_format", "json",
"-show_format",
"-show_streams",
video_path,
])
.output()
.map_err(|e| format!("Failed to run ffprobe: {}. Is FFmpeg installed and in PATH?", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("ffprobe failed: {}", stderr));
}
let json_str = String::from_utf8_lossy(&output.stdout);
let value: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| format!("Failed to parse ffprobe output: {}", e))?;
// Get format info
let format = value.get("format").ok_or("No fextract_frame function · rust · L144-L179 (36 LOC)src-tauri/src/services/video_processor.rs
pub fn extract_frame(video_path: &str, timestamp_sec: f64) -> Result<String, String> {
if !Path::new(video_path).exists() {
return Err(format!("Video file not found: {}", video_path));
}
// Use ffmpeg to extract a single frame as PNG to stdout
let output = Command::new("ffmpeg")
.args([
"-ss", ×tamp_sec.to_string(),
"-i", video_path,
"-vframes", "1",
"-q:v", "2",
"-f", "image2pipe",
"-",
])
.output()
.map_err(|e| format!("Failed to run ffmpeg: {}", e))?;
if !output.status.success() {
// ffmpeg outputs to stderr even on success, check if we got any stdout data
if output.stdout.is_empty() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("ffmpeg failed: {}", stderr));
}
}
// The output should be PNG data
let png_data = output.stdout;
if png_data.is_empty() {
Repobility · open methodology · https://repobility.com/research/
extract_frames function · rust · L190-L216 (27 LOC)src-tauri/src/services/video_processor.rs
pub fn extract_frames(
video_path: &str,
duration_sec: f64,
count: usize,
) -> Result<Vec<(f64, String)>, String> {
let interval = if count > 0 {
duration_sec / count as f64
} else {
1.0
};
let mut frames = Vec::new();
for i in 0..count {
let timestamp = interval * i as f64;
match extract_frame(video_path, timestamp) {
Ok(frame_data) => {
frames.push((timestamp, frame_data));
}
Err(e) => {
eprintln!("Failed to extract frame at {}: {}", timestamp, e);
}
}
}
Ok(frames)
}extract_frames_async function · rust · L219-L235 (17 LOC)src-tauri/src/services/video_processor.rs
pub fn extract_frames_async(
video_path: String,
duration_sec: f64,
count: usize,
app: AppHandle,
) {
thread::spawn(move || {
match extract_frames(&video_path, duration_sec, count) {
Ok(frames) => {
let _ = app.emit("frames-extracted", frames);
}
Err(e) => {
let _ = app.emit("frame-extraction-error", e);
}
}
});
}test_get_video_metadata_file_not_found function · rust · L242-L246 (5 LOC)src-tauri/src/services/video_processor.rs
fn test_get_video_metadata_file_not_found() {
let result = get_video_metadata("/nonexistent/path/to/video.mp4");
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}test_extract_frame_file_not_found function · rust · L249-L252 (4 LOC)src-tauri/src/services/video_processor.rs
fn test_extract_frame_file_not_found() {
let result = extract_frame("/nonexistent/video.mp4", 1.0);
assert!(result.is_err());
}test_extract_frames_interval_calculation function · rust · L255-L260 (6 LOC)src-tauri/src/services/video_processor.rs
fn test_extract_frames_interval_calculation() {
let duration = 10.0;
let count = 5;
let expected_interval = duration / count as f64;
assert_eq!(expected_interval, 2.0);
}default function · rust · L47-L56 (10 LOC)src-tauri/src/services/video_stitcher.rs
fn default() -> Self {
Self {
output_path: "output.mp4".to_string(),
target_resolution: None,
target_fps: None,
codec: "libx264".to_string(),
crf: 23,
normalize: false,
}
}proxy function · rust · L61-L70 (10 LOC)src-tauri/src/services/video_stitcher.rs
pub fn proxy(output_path: String) -> Self {
Self {
output_path,
target_resolution: Some((PROXY_WIDTH, PROXY_HEIGHT)),
target_fps: Some(PROXY_FPS),
codec: "libx264".to_string(),
crf: 28, // Lower quality for proxy (smaller file)
normalize: true,
}
}normalize_video function · rust · L103-L141 (39 LOC)src-tauri/src/services/video_stitcher.rs
pub fn normalize_video(input_path: &str, output_path: &str) -> Result<(), String> {
if !Path::new(input_path).exists() {
return Err(format!("Input video not found: {}", input_path));
}
// FFmpeg command to normalize video
let mut cmd = Command::new("ffmpeg");
cmd.arg("-i").arg(input_path);
// Video: scale to 720p, set fps to 30
cmd.arg("-vf").arg(format!("scale={}:{}", PROXY_WIDTH, PROXY_HEIGHT));
cmd.arg("-r").arg(PROXY_FPS.to_string());
// Video codec settings
cmd.arg("-c:v").arg("libx264");
cmd.arg("-preset").arg("fast");
cmd.arg("-crf").arg("28"); // Lower quality for proxy
// Audio: normalize to AAC
cmd.arg("-c:a").arg("aac");
cmd.arg("-b:a").arg("128k");
cmd.arg("-ar").arg("48000"); // Standard sample rate
// Fast start for web playback
cmd.arg("-movflags").arg("faststart");
cmd.arg(output_path);
cmd.arg("-y"); // Overwrite
let output = cmd.output()
.map_err(|e| format!("FaiRepobility analyzer · published findings · https://repobility.com
stitch_videos function · rust · L151-L217 (67 LOC)src-tauri/src/services/video_stitcher.rs
pub fn stitch_videos(video_paths: &[String], config: &StitchConfig) -> Result<StitchResult, String> {
if video_paths.is_empty() {
return Err("No videos to stitch".to_string());
}
if video_paths.len() == 1 {
// Single video - normalize (or copy based on config)
if config.normalize {
normalize_video(&video_paths[0], &config.output_path)?;
} else {
copy_video(&video_paths[0], &config.output_path)?;
}
return get_result(&config.output_path);
}
// If normalizing, first normalize all clips
let paths_to_stitch = if config.normalize {
let normalized_paths = normalize_all_videos(video_paths)?;
normalized_paths
} else {
video_paths.to_vec()
};
// Create concat file for FFmpeg
let concat_file = create_concat_file(&paths_to_stitch)?;
// Build FFmpeg command for concat
let mut cmd = Command::new("ffmpeg");
cmd.arg("-f").arg("concat");
cmd.arg("-snormalize_all_videos function · rust · L220-L237 (18 LOC)src-tauri/src/services/video_stitcher.rs
fn normalize_all_videos(video_paths: &[String]) -> Result<Vec<String>, String> {
let temp_dir = std::env::temp_dir().join("video_editor_norm");
fs::create_dir_all(&temp_dir)
.map_err(|e| format!("Failed to create temp dir: {}", e))?;
let mut normalized_paths = Vec::new();
for (i, input_path) in video_paths.iter().enumerate() {
let output_path = temp_dir.join(format!("normalized_{}.mp4", i));
let output_str = output_path.to_str()
.ok_or("Invalid temp path")?
.to_string();
normalize_video(input_path, &output_str)?;
normalized_paths.push(output_str);
}
Ok(normalized_paths)
}cleanup_normalized_videos function · rust · L240-L250 (11 LOC)src-tauri/src/services/video_stitcher.rs
fn cleanup_normalized_videos(normalized_paths: &[String], original_paths: &[String]) {
for path in normalized_paths {
// Only delete if it's a temp file (not an original)
if !original_paths.contains(path) {
let _ = std::fs::remove_file(path);
}
}
// Try to remove temp dir
let temp_dir = std::env::temp_dir().join("video_editor_norm");
let _ = fs::remove_dir(temp_dir);
}