out.push(arg.to_string());
if !inserted && arg == "-nostdin" {
out.push("-threads".to_string());
out.push("1".to_string());
inserted = true;
}
}
if !inserted {
out.insert(0, "1".to_string());
out.insert(0, "-threads".to_string());
}
out
}
fn ffmpeg_slot_map() -> &'static Mutex<HashMap<String, Arc<FfmpegVideoSlot>>> {
FFMPEG_PER_VIDEO.get_or_init(|| Mutex::new(HashMap::new()))
}
// Matches TS mediaPathsMatch (backslashes + lowercase); Linux dev may fold distinct paths.
fn normalize_media_key(path: &str) -> String {
path.replace('/', "\\").to_lowercase()
}
async fn ffmpeg_slot_for(video_path: &str) -> Arc<FfmpegVideoSlot> {
let key = normalize_media_key(video_path);
let mut map = ffmpeg_slot_map().lock().await;
map.entry(key)
.or_insert_with(|| {
Arc::new(FfmpegVideoSlot {
lock: Arc::new(Mutex::new(())),
child: Arc::new(Mutex::new(None)),
})
})
.clone()
}
/// Stop any in-flight RuForge ffmpeg sidecar for this file (preview sprites / poster).
pub async fn cancel_ffmpeg_for_video(video_path: &str) {
Built with
FFmpeg Processing
ffmpeg and ffprobe ship as bundled sidecars for yt-dlp muxing, scrubber sprite sheets, and quiet metadata probes.
yt-dlp invokes ffmpeg when a download needs merge or post-process steps. RuForge watches stdout after the byte bar hits 100% and keeps the queue row in a Processing phase until the child process exits. Audio-only jobs skip video post-process paths.
Scrubber hover previews are local sprite sheets. Settings → Downloads auto scrubber previews (default on) tells downloader.rs to call extract_frames after a successful video finish. The ffmpeg filter chain samples one frame every five seconds into 160×90 tiles on a 10×10 grid (fps=1/5,scale=160:90,tile=10x10). Sheets land under .ruforge/thumbs/ beside the video.
useScrubberThumbs.ts loads sheet paths and maps hover position to a tile. Chapter scrubber and simple scrubber both use the same hook; Generate Previews in the library calls the same Rust command when auto mode is off. Completing a sheet emits scrub-sprites-updated so open players reload without restart.
ffprobe warms a disk cache (ffprobe-hints under app data). The player UI does not show codec strings today. Delete and replace flows cancel in-flight ffmpeg work via the per-file lock so files are not left locked on disk.
In the repo
Where it shows up
- Sidecars
binaries/ffmpeg-*andbinaries/ffprobe-*intauri.conf.json -
src-tauri/src/commands/media.rssprites, posters, ffprobe cache -
useScrubberThumbs.ts,ChapterScrubber.tsx,ScrubHoverPreview.tsx - Settings → Downloads auto-preview toggle (
autoScrubPreviewsin store settings)