use crate::media::{
build_command, duration_progress_label, human_size, media_kind_label, path_extension_lower,
probe_duration, start_operation, suggest_output_path, supported_audio_extension,
supported_media_extension, supported_video_extension, AudioFormat, ConversionMode,
ConversionRequest, OperationEvent, QualityPreset, VideoFormat,
};
use eframe::egui::{
self, Align2, Color32, FontId, Pos2, Rect, RichText, Rounding, Sense, Shape, Stroke, Vec2,
};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Receiver;
use std::time::Duration;
pub struct CaeryApp {
input_path: String,
output_path: String,
last_suggested_output: String,
mode: ConversionMode,
audio_format: AudioFormat,
video_format: VideoFormat,
quality: QualityPreset,
overwrite_output: bool,
final_confirm_open: bool,
logs: Vec<String>,
operation_rx: Option<Receiver<OperationEvent>>,
operation_running: bool,
operation_status: String,
progress: Option<(f64, f64)>,
media_browser: MediaBrowserState,
}
#[derive(Debug, Clone)]
struct MediaBrowserState {
open: bool,
current_dir: PathBuf,
entries: Vec<BrowserEntry>,
error: Option<String>,
}
impl MediaBrowserState {
fn new() -> Self {
Self {
open: false,
current_dir: initial_browser_directory(""),
entries: Vec::new(),
error: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct BrowserEntry {
name: String,
path: PathBuf,
kind: BrowserEntryKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum BrowserEntryKind {
Directory,
Media,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum BrowserAction {
None,
Parent,
Refresh,
OpenDirectory(PathBuf),
SelectMedia(PathBuf),
Close,
}
impl CaeryApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
cc.egui_ctx.set_visuals(knott_visuals());
Self {
input_path: String::new(),
output_path: String::new(),
last_suggested_output: String::new(),
mode: ConversionMode::ExtractAudio,
audio_format: AudioFormat::Mp3,
video_format: VideoFormat::Mp4,
quality: QualityPreset::Balanced,
overwrite_output: false,
final_confirm_open: false,
logs: vec!["CAERY ONLINE // FFMPEG ROUTE IDLE".to_owned()],
operation_rx: None,
operation_running: false,
operation_status: "READY".to_owned(),
progress: None,
media_browser: MediaBrowserState::new(),
}
}
fn current_request(&self) -> ConversionRequest {
ConversionRequest {
mode: self.mode,
input_path: PathBuf::from(self.input_path.trim()),
output_path: PathBuf::from(self.output_path.trim()),
audio_format: self.audio_format,
video_format: self.video_format,
quality: self.quality,
overwrite: self.overwrite_output,
}
}
fn can_start(&self) -> bool {
!self.operation_running && build_command(&self.current_request()).is_ok()
}
fn drain_operation_events(&mut self) {
let mut finished = false;
if let Some(rx) = self.operation_rx.take() {
while let Ok(event) = rx.try_recv() {
match event {
OperationEvent::Started(command) => {
self.operation_status = "ENCODING".to_owned();
self.push_log(format!("START // {command}"));
}
OperationEvent::Log(line) => {
let lower = line.to_ascii_lowercase();
if lower.contains("input #") {
self.operation_status = "READING SOURCE".to_owned();
} else if lower.contains("output #") {
self.operation_status = "WRITING OUTPUT".to_owned();
} else if lower.contains("video:") || lower.contains("audio:") {
self.operation_status = "FINALIZING".to_owned();
}
self.push_log(line.to_ascii_uppercase());
}
OperationEvent::Progress {
current_seconds,
total_seconds,
} => {
self.progress = Some((current_seconds, total_seconds));
self.operation_status =
duration_progress_label(current_seconds, total_seconds);
}
OperationEvent::Finished(result) => {
finished = true;
self.operation_running = false;
match result {
Ok(()) => {
if let Some((_, total)) = self.progress {
self.progress = Some((total, total));
}
self.operation_status = "COMPLETE".to_owned();
self.push_log("CONVERSION COMPLETE // OUTPUT READY".to_owned());
}
Err(error) => {
self.operation_status = "FAILED".to_owned();
self.push_log(format!("CONVERSION FAILED // {error}"));
}
}
}
}
}
if !finished {
self.operation_rx = Some(rx);
}
}
}
fn start_selected_operation(&mut self) {
if !self.can_start() {
self.final_confirm_open = false;
self.push_log("PRE-FLIGHT CHECK INCOMPLETE // CONVERSION BLOCKED".to_owned());
return;
}
let request = self.current_request();
match build_command(&request) {
Ok(command) => {
let expected_duration = probe_duration(&request.input_path);
self.logs.clear();
self.progress = None;
self.final_confirm_open = false;
self.operation_running = true;
self.operation_status = if expected_duration.is_some() {
"STARTING".to_owned()
} else {
"STARTING // NO DURATION PROBE".to_owned()
};
self.operation_rx = Some(start_operation(command, expected_duration));
}
Err(error) => self.push_log(format!("CANNOT START // {error}")),
}
}
fn push_log(&mut self, line: String) {
self.logs.push(line);
if self.logs.len() > 250 {
let overflow = self.logs.len() - 250;
self.logs.drain(0..overflow);
}
}
fn open_media_browser(&mut self) {
self.media_browser.open = true;
self.media_browser.current_dir = initial_browser_directory(&self.input_path);
self.refresh_browser_entries();
}
fn refresh_browser_entries(&mut self) {
match scan_browser_directory(&self.media_browser.current_dir) {
Ok(entries) => {
self.media_browser.entries = entries;
self.media_browser.error = None;
}
Err(error) => {
self.media_browser.entries.clear();
self.media_browser.error = Some(error.to_string());
}
}
}
fn set_browser_dir(&mut self, path: PathBuf) {
self.media_browser.current_dir = path;
self.refresh_browser_entries();
}
fn set_input_path(&mut self, path: PathBuf) {
self.input_path = path.display().to_string();
self.refresh_suggested_output(true);
let detail = fs::metadata(&path)
.ok()
.map(|metadata| format!(" // {}", human_size(metadata.len())))
.unwrap_or_default();
self.push_log(format!("SOURCE SELECTED // {}{}", self.input_path, detail));
}
fn refresh_suggested_output(&mut self, force: bool) {
let suggestion = suggest_output_path(
Path::new(self.input_path.trim()),
self.mode,
self.audio_format,
self.video_format,
)
.map(|path| path.display().to_string())
.unwrap_or_default();
if force
|| self.output_path.trim().is_empty()
|| self.output_path == self.last_suggested_output
{
self.output_path = suggestion.clone();
}
self.last_suggested_output = suggestion;
}
fn handle_dropped_files(&mut self, ctx: &egui::Context) {
let dropped_files = ctx.input(|input| input.raw.dropped_files.clone());
for file in dropped_files {
if let Some(path) = file.path {
if supported_media_extension(&path) {
self.set_input_path(path);
break;
}
}
}
}
fn handle_browser_action(&mut self, action: BrowserAction) {
match action {
BrowserAction::None => {}
BrowserAction::Parent => {
if let Some(parent) = self.media_browser.current_dir.parent() {
self.set_browser_dir(parent.to_path_buf());
}
}
BrowserAction::Refresh => self.refresh_browser_entries(),
BrowserAction::OpenDirectory(path) => self.set_browser_dir(path),
BrowserAction::SelectMedia(path) => {
self.set_input_path(path);
self.media_browser.open = false;
}
BrowserAction::Close => self.media_browser.open = false,
}
}
}
impl eframe::App for CaeryApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.handle_dropped_files(ctx);
self.drain_operation_events();
let phase = ctx.input(|input| input.time) as f32;
ctx.request_repaint_after(Duration::from_millis(if self.operation_running {
16
} else {
33
}));
egui::CentralPanel::default()
.frame(egui::Frame::none().fill(kd_black()))
.show(ctx, |ui| {
let viewport_width = ui.available_width();
let viewport_height = ui.available_height();
let compact = viewport_width < 1040.0 || viewport_height < 760.0;
paint_background(ui, phase);
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.show(ui, |ui| {
ui.set_min_width(viewport_width);
system_status_bar(
ui,
self.operation_running,
&self.operation_status,
phase,
);
ui.add_space(if compact { 10.0 } else { 18.0 });
self.header(ui, phase, compact);
ui.add_space(if compact { 10.0 } else { 16.0 });
if viewport_width < 980.0 {
self.source_panel(ui, phase, compact);
ui.add_space(14.0);
self.route_panel(ui, phase, compact);
} else {
ui.columns(2, |columns| {
columns[0].vertical(|ui| self.source_panel(ui, phase, compact));
columns[1].vertical(|ui| self.route_panel(ui, phase, compact));
});
}
});
});
self.media_browser_window(ctx, phase);
self.final_confirmation_window(ctx, phase);
}
}
impl CaeryApp {
fn header(&mut self, ui: &mut egui::Ui, phase: f32, compact: bool) {
let header_height = if compact { 132.0 } else { 178.0 };
let (rect, _) = ui.allocate_exact_size(
Vec2::new(ui.available_width(), header_height),
Sense::hover(),
);
let painter = ui.painter_at(rect);
painter.rect_filled(rect, Rounding::same(2.0), Color32::from_rgb(3, 3, 3));
painter.rect_stroke(rect, Rounding::ZERO, Stroke::new(1.0, kd_border()));
draw_corner_brackets(&painter, rect, 34.0, Color32::from_gray(122));
draw_header_sweep(&painter, rect, phase);
if rect.width() > 760.0 {
let mark_offset = if compact { -92.0 } else { -116.0 };
let mark_center = rect.right_center() + egui::vec2(mark_offset, 0.0);
draw_trefoil_mark(
&painter,
mark_center,
if compact { 25.0 } else { 32.0 },
phase,
);
draw_crosshair(
&painter,
mark_center,
if compact { 58.0 } else { 78.0 },
Color32::from_rgba_premultiplied(255, 255, 255, 48),
);
painter.text(
mark_center + egui::vec2(0.0, if compact { 48.0 } else { 62.0 }),
Align2::CENTER_CENTER,
"KNOTT DYNAMICS",
FontId::monospace(if compact { 9.0 } else { 11.0 }),
Color32::from_gray(176),
);
}
painter.text(
rect.left_top() + egui::vec2(28.0, 22.0),
Align2::LEFT_TOP,
"CAERY",
FontId::proportional(if compact { 44.0 } else { 60.0 }),
Color32::from_rgb(246, 246, 246),
);
painter.text(
rect.left_top() + egui::vec2(31.0, if compact { 72.0 } else { 88.0 }),
Align2::LEFT_TOP,
"STRENGTH IN STRUCTURE // RUST MEDIA SYSTEM",
FontId::monospace(if compact { 11.0 } else { 14.0 }),
Color32::from_gray(172),
);
painter.text(
rect.left_top() + egui::vec2(31.0, if compact { 96.0 } else { 116.0 }),
Align2::LEFT_TOP,
"FFMPEG CONVERSION ROUTER / AUDIO EXTRACTION / TRANSCODE CONTROL",
FontId::monospace(11.0),
Color32::from_gray(96),
);
if !compact {
painter.text(
rect.left_top() + egui::vec2(31.0, 140.0),
Align2::LEFT_TOP,
"ROUTE PATH: SOURCE -> CODEC -> CONTAINER -> OUTPUT // NO BLOCK DEVICE WRITES",
FontId::monospace(9.0),
Color32::from_gray(58),
);
painter.text(
rect.left_bottom() + egui::vec2(30.0, -12.0),
Align2::LEFT_BOTTOM,
"REV 0.1 // MEDIA CONVERSION STACK // VERIFY OUTPUT PATH BEFORE WRITE",
FontId::monospace(10.0),
Color32::from_gray(70),
);
}
let status = if self.operation_running {
self.operation_status.as_str()
} else if source_file_ready(&self.input_path, self.mode) {
"SOURCE LOCKED"
} else {
"AWAITING SOURCE"
};
if rect.width() > 760.0 {
draw_status_pill(
&painter,
rect.right_top() + egui::vec2(-250.0, 24.0),
status,
);
}
}
fn source_panel(&mut self, ui: &mut egui::Ui, phase: f32, compact: bool) {
let response = kd_panel(ui, |ui| {
panel_heading(ui, "SOURCE VAULT", "LOCAL MEDIA");
ui.add_space(10.0);
let mut browse_clicked = false;
let mut clear_clicked = false;
ui.horizontal(|ui| {
field_label(ui, self.mode.source_label());
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui
.add_enabled(
!self.operation_running,
kd_button("OPEN MEDIA VAULT", false),
)
.clicked()
{
browse_clicked = true;
}
if ui
.add_enabled(
!self.operation_running && !self.input_path.trim().is_empty(),
kd_button("CLEAR", false),
)
.clicked()
{
clear_clicked = true;
}
});
});
let source_edit = egui::TextEdit::singleline(&mut self.input_path)
.hint_text("/home/me/Videos/source.mp4 or drag media here")
.desired_width(f32::INFINITY);
if ui
.add_enabled(!self.operation_running, source_edit)
.changed()
{
self.refresh_suggested_output(false);
}
ui.add_space(9.0);
media_preview_card(ui, self.input_path.trim(), self.mode, phase);
ui.add_space(14.0);
ui.horizontal(|ui| {
field_label(ui, "OUTPUT FILE");
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui
.add_enabled(!self.operation_running, kd_button("AUTO NAME", false))
.clicked()
{
self.refresh_suggested_output(true);
}
});
});
let output_edit = egui::TextEdit::singleline(&mut self.output_path)
.hint_text("/home/me/Videos/source-caery.mp3")
.desired_width(f32::INFINITY);
ui.add_enabled(!self.operation_running, output_edit);
ui.add_space(9.0);
output_preview_card(
ui,
self.output_path.trim(),
self.mode,
self.audio_format,
self.video_format,
self.overwrite_output,
phase,
);
if browse_clicked {
self.open_media_browser();
}
if clear_clicked {
self.input_path.clear();
self.refresh_suggested_output(true);
self.push_log("SOURCE CLEARED".to_owned());
}
ui.add_space(12.0);
tech_note(
ui,
"ROUTE SOURCE",
"DRAG MEDIA INTO THE WINDOW OR USE MEDIA VAULT TO SELECT LOCAL FILES.",
);
});
draw_outer_glow(ui.painter(), response.rect, 2, 20.0, 10);
draw_corner_brackets(ui.painter(), response.rect, 22.0, Color32::from_gray(88));
if compact {
ui.add_space(2.0);
}
}
fn route_panel(&mut self, ui: &mut egui::Ui, phase: f32, compact: bool) {
let response = kd_panel(ui, |ui| {
panel_heading(ui, "CONVERSION ROUTE", "CODEC MATRIX");
ui.add_space(10.0);
tech_note(ui, self.mode.title(), self.mode.note_text());
ui.add_space(10.0);
let mut selected_mode = self.mode;
for mode in ConversionMode::ALL {
let selected = self.mode == mode;
let response = mode_chip(ui, mode.title(), mode.description(), selected, phase);
if response.clicked() && !self.operation_running {
selected_mode = mode;
}
ui.add_space(8.0);
}
if selected_mode != self.mode {
self.mode = selected_mode;
self.refresh_suggested_output(false);
}
ui.add_space(10.0);
if format_controls(
ui,
self.mode,
&mut self.audio_format,
&mut self.video_format,
&mut self.quality,
self.operation_running,
) {
self.refresh_suggested_output(false);
}
ui.add_space(14.0);
output_write_box(
ui,
self.output_path.trim(),
&mut self.overwrite_output,
self.operation_running,
);
ui.add_space(10.0);
readiness_panel(
ui,
source_file_ready(&self.input_path, self.mode),
output_path_ready(
&self.output_path,
self.mode
.output_extension(self.audio_format, self.video_format),
self.overwrite_output,
),
self.mode,
);
ui.add_space(12.0);
if let Some((current, total)) = self.progress {
animated_progress(ui, current, total, phase);
ui.add_space(8.0);
} else if self.operation_running {
indeterminate_progress(ui, phase);
ui.add_space(8.0);
}
let can_start = self.can_start();
let button_text = if self.operation_running {
"RUNNING"
} else {
"REVIEW + CONVERT"
};
if ui
.add_enabled(can_start, kd_button(button_text, true))
.clicked()
{
self.final_confirm_open = true;
}
if !can_start && !self.operation_running {
ui.add_space(8.0);
ui.label(
RichText::new(
"SELECT SOURCE, VALID OUTPUT PATH, ROUTE FORMAT, THEN REVIEW THE CONVERSION.",
)
.size(11.0)
.monospace()
.color(Color32::from_gray(112)),
);
}
ui.add_space(14.0);
log_panel(ui, &self.logs, compact);
});
draw_outer_glow(ui.painter(), response.rect, 2, 20.0, 10);
draw_corner_brackets(ui.painter(), response.rect, 22.0, Color32::from_gray(88));
}
fn media_browser_window(&mut self, ctx: &egui::Context, phase: f32) {
if !self.media_browser.open {
return;
}
let mut open = self.media_browser.open;
let mut action = BrowserAction::None;
egui::Window::new("MEDIA VAULT // LOCAL FILESYSTEM")
.open(&mut open)
.collapsible(false)
.resizable(true)
.default_size(Vec2::new(820.0, 560.0))
.frame(
egui::Frame::none()
.fill(Color32::from_rgba_premultiplied(0, 0, 0, 245))
.stroke(Stroke::new(1.0, Color32::from_gray(120)))
.inner_margin(egui::Margin::same(16.0)),
)
.show(ctx, |ui| {
action = media_browser_contents(ui, &self.media_browser, phase);
});
self.media_browser.open = open;
self.handle_browser_action(action);
}
fn final_confirmation_window(&mut self, ctx: &egui::Context, phase: f32) {
if !self.final_confirm_open {
return;
}
let request = self.current_request();
let operation = self.mode.title().to_ascii_uppercase();
let source = if self.input_path.trim().is_empty() {
"NO SOURCE".to_owned()
} else {
self.input_path.clone()
};
let output = if self.output_path.trim().is_empty() {
"NO OUTPUT".to_owned()
} else {
self.output_path.clone()
};
let format = match self.mode {
ConversionMode::ExtractAudio | ConversionMode::TranscodeAudio => {
self.audio_format.label().to_owned()
}
ConversionMode::TranscodeVideo => self.video_format.label().to_owned(),
};
let overwrite = if request.output_path.exists() {
if self.overwrite_output {
"EXISTING OUTPUT WILL BE REPLACED"
} else {
"OUTPUT EXISTS // OVERWRITE NOT ENABLED"
}
} else {
"NEW OUTPUT FILE"
};
let mut open = self.final_confirm_open;
let mut submit = false;
let mut cancel = false;
egui::Window::new("FINAL ROUTE CHECK // START CONVERSION?")
.open(&mut open)
.collapsible(false)
.resizable(false)
.default_size(Vec2::new(640.0, 390.0))
.frame(
egui::Frame::none()
.fill(Color32::from_rgba_premultiplied(0, 0, 0, 248))
.stroke(Stroke::new(1.0, Color32::from_gray(176)))
.inner_margin(egui::Margin::same(18.0)),
)
.show(ctx, |ui| {
let rect = ui.max_rect();
draw_corner_brackets(
ui.painter(),
rect.shrink(2.0),
28.0,
Color32::from_gray(128),
);
panel_heading(ui, "ROUTE SUBMIT", "VERIFY MEDIA WRITE");
ui.add_space(12.0);
final_warning_card(ui, phase);
ui.add_space(12.0);
tech_note(ui, "OPERATION", &operation);
ui.add_space(8.0);
tech_note(ui, "SOURCE", &source);
ui.add_space(8.0);
tech_note(ui, "OUTPUT", &output);
ui.add_space(8.0);
tech_note(
ui,
"FORMAT / QUALITY",
&format!("{} // {}", format, self.quality.label()),
);
ui.add_space(8.0);
tech_note(ui, "OUTPUT POLICY", overwrite);
ui.add_space(16.0);
ui.horizontal(|ui| {
if ui.add(kd_button("CANCEL", false)).clicked() {
cancel = true;
}
if ui
.add_enabled(self.can_start(), kd_button("START CONVERSION", true))
.clicked()
{
submit = true;
}
});
});
self.final_confirm_open = open && !cancel && !submit;
if cancel {
self.push_log("FINAL ROUTE CHECK CANCELLED".to_owned());
}
if submit {
self.start_selected_operation();
}
}
}
fn knott_visuals() -> egui::Visuals {
let mut visuals = egui::Visuals::dark();
visuals.override_text_color = None;
visuals.panel_fill = kd_black();
visuals.window_fill = kd_black();
visuals.extreme_bg_color = kd_black();
visuals.faint_bg_color = Color32::from_rgb(8, 8, 8);
visuals.widgets.noninteractive.bg_fill = Color32::from_rgb(8, 8, 8);
visuals.widgets.inactive.bg_fill = Color32::from_rgb(12, 12, 12);
visuals.widgets.hovered.bg_fill = Color32::from_rgb(24, 24, 24);
visuals.widgets.active.bg_fill = Color32::from_rgb(34, 34, 34);
visuals.widgets.noninteractive.fg_stroke = Stroke::new(1.0, Color32::from_gray(220));
visuals.widgets.inactive.fg_stroke = Stroke::new(1.0, Color32::from_gray(188));
visuals.widgets.hovered.fg_stroke = Stroke::new(1.0, Color32::WHITE);
visuals.widgets.active.fg_stroke = Stroke::new(1.0, Color32::WHITE);
visuals.selection.bg_fill = Color32::from_gray(232);
visuals.selection.stroke = Stroke::new(1.0, Color32::BLACK);
visuals
}
fn kd_black() -> Color32 {
Color32::from_rgb(0, 0, 0)
}
fn kd_surface() -> Color32 {
Color32::from_rgb(8, 8, 8)
}
fn kd_panel_fill() -> Color32 {
Color32::from_rgba_premultiplied(6, 6, 6, 232)
}
fn kd_border() -> Color32 {
Color32::from_rgb(32, 32, 32)
}
fn kd_green() -> Color32 {
Color32::from_rgb(0, 255, 136)
}
fn draw_outer_glow(
painter: &egui::Painter,
rect: Rect,
passes: usize,
spread: f32,
base_alpha: u8,
) {
for pass in 0..passes {
let t = pass as f32 / passes.max(1) as f32;
let alpha = ((base_alpha as f32) * (1.0 - t * 0.72)).round() as u8;
painter.rect_stroke(
rect.expand(spread * (t + 0.35)),
Rounding::same(2.0),
Stroke::new(1.0, Color32::from_rgba_premultiplied(255, 255, 255, alpha)),
);
}
}
fn draw_header_sweep(painter: &egui::Painter, rect: Rect, phase: f32) {
let sweep = ((phase * 0.18).fract() * (rect.width() + 260.0)) - 130.0;
let x = rect.left() + sweep;
painter.line_segment(
[
Pos2::new(x, rect.top() + 8.0),
Pos2::new(x + 130.0, rect.bottom() - 8.0),
],
Stroke::new(1.4, Color32::from_rgba_premultiplied(255, 255, 255, 32)),
);
painter.line_segment(
[
Pos2::new(x - 24.0, rect.top() + 8.0),
Pos2::new(x + 106.0, rect.bottom() - 8.0),
],
Stroke::new(0.6, Color32::from_rgba_premultiplied(255, 255, 255, 18)),
);
}
fn paint_background(ui: &mut egui::Ui, phase: f32) {
let rect = ui.max_rect();
let painter = ui.painter().clone();
painter.rect_filled(rect, Rounding::ZERO, kd_black());
let spacing = 52.0;
let mut x = rect.left() - rect.left().rem_euclid(spacing);
while x <= rect.right() {
painter.line_segment(
[Pos2::new(x, rect.top()), Pos2::new(x, rect.bottom())],
Stroke::new(1.0, Color32::from_rgba_premultiplied(255, 255, 255, 8)),
);
x += spacing;
}
let mut y = rect.top() - rect.top().rem_euclid(spacing);
while y <= rect.bottom() {
painter.line_segment(
[Pos2::new(rect.left(), y), Pos2::new(rect.right(), y)],
Stroke::new(1.0, Color32::from_rgba_premultiplied(255, 255, 255, 8)),
);
y += spacing;
}
draw_algorithmic_botany(&painter, rect, phase);
for (index, label) in [
"R:0.117 SIGMA:0.884 EPS:0.031",
"FFMPEG ROUTE // LOCAL MEDIA ONLY",
"CAERY-RS // KD VISUAL PROTOCOL",
]
.iter()
.enumerate()
{
painter.text(
rect.left_top() + egui::vec2(14.0, 88.0 + index as f32 * 170.0),
Align2::LEFT_TOP,
*label,
FontId::monospace(9.0),
Color32::from_gray(42),
);
}
}
fn draw_algorithmic_botany(painter: &egui::Painter, rect: Rect, phase: f32) {
let vines = [
(
rect.left_top() + egui::vec2(70.0, 185.0),
0.58,
rect.height().min(720.0) * 0.18,
0.0,
),
(
rect.left_bottom() + egui::vec2(80.0, -6.0),
-1.18,
rect.height().min(720.0) * 0.18,
0.67,
),
(
rect.left_bottom() + egui::vec2(rect.width() * 0.49, -8.0),
-1.88,
rect.height().min(720.0) * 0.18,
1.34,
),
];
for (index, (base, base_angle, length, offset)) in vines.iter().enumerate() {
let growth = ping_pong(phase * 0.075 + offset);
let angle = base_angle + (phase * 0.08 + index as f32).sin() * 0.025;
draw_branch(
painter,
*base,
angle,
*length,
0,
5,
growth,
Color32::from_rgba_premultiplied(255, 255, 255, 22),
);
}
}
fn ping_pong(value: f32) -> f32 {
let value = value.rem_euclid(2.0);
if value <= 1.0 {
value
} else {
2.0 - value
}
}
fn draw_branch(
painter: &egui::Painter,
start: Pos2,
angle: f32,
length: f32,
depth: usize,
max_depth: usize,
growth: f32,
color: Color32,
) {
if depth > max_depth || length < 7.0 {
return;
}
let stage = growth * (max_depth as f32 + 1.0) - depth as f32;
let visible = stage.clamp(0.0, 1.0);
if visible <= 0.0 {
return;
}
let alpha = color.a().saturating_sub((depth as u8) * 3);
let stroke = Stroke::new(
(0.95 - depth as f32 * 0.09).max(0.3),
Color32::from_rgba_premultiplied(255, 255, 255, alpha),
);
let full_end = start + egui::vec2(angle.cos() * length, angle.sin() * length);
let end = start + (full_end - start) * visible;
painter.line_segment([start, end], stroke);
if visible < 1.0 {
return;
}
let next = length * 0.66;
let spread = 0.42 + depth as f32 * 0.035;
draw_branch(
painter,
end,
angle - spread,
next,
depth + 1,
max_depth,
growth,
color,
);
draw_branch(
painter,
end,
angle + spread * 0.78,
next * 0.9,
depth + 1,
max_depth,
growth,
color,
);
}
fn system_status_bar(ui: &mut egui::Ui, running: bool, status: &str, phase: f32) {
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 30.0), Sense::hover());
let painter = ui.painter_at(rect);
painter.rect_filled(
rect,
Rounding::ZERO,
Color32::from_rgba_premultiplied(0, 0, 0, 235),
);
painter.line_segment(
[rect.left_bottom(), rect.right_bottom()],
Stroke::new(1.0, kd_border()),
);
let dot = if running {
((phase * 8.0).sin() * 0.5 + 0.5) * 3.0 + 3.0
} else {
4.0
};
painter.circle_filled(rect.left_center() + egui::vec2(16.0, 0.0), dot, kd_green());
painter.text(
rect.left_center() + egui::vec2(30.0, 0.0),
Align2::LEFT_CENTER,
format!("ONLINE // SYS:CAERY // BUILD:0.1 // {status}"),
FontId::monospace(11.0),
Color32::from_gray(186),
);
if rect.width() > 820.0 {
painter.text(
rect.right_center() + egui::vec2(-12.0, 0.0),
Align2::RIGHT_CENTER,
"KNOTT DYNAMICS AESTHETIC // SILVER-ON-BLACK",
FontId::monospace(10.0),
Color32::from_gray(78),
);
}
}
fn kd_panel<R>(ui: &mut egui::Ui, add_contents: impl FnOnce(&mut egui::Ui) -> R) -> egui::Response {
egui::Frame::none()
.fill(kd_panel_fill())
.rounding(Rounding::same(2.0))
.stroke(Stroke::new(1.0, kd_border()))
.inner_margin(egui::Margin::same(18.0))
.show(ui, add_contents)
.response
}
fn panel_heading(ui: &mut egui::Ui, tag: &str, title: &str) {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.label(
RichText::new(tag)
.monospace()
.size(10.0)
.color(Color32::from_gray(112)),
);
ui.label(
RichText::new(title)
.size(24.0)
.strong()
.color(Color32::from_rgb(244, 244, 244)),
);
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let (rect, _) = ui.allocate_exact_size(Vec2::new(74.0, 24.0), Sense::hover());
let painter = ui.painter_at(rect);
painter.rect_stroke(
rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(64)),
);
painter.text(
rect.center(),
Align2::CENTER_CENTER,
"MK.IV",
FontId::monospace(10.0),
Color32::from_gray(150),
);
});
});
ui.add_space(4.0);
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 1.0), Sense::hover());
ui.painter().line_segment(
[rect.left_center(), rect.right_center()],
Stroke::new(1.0, Color32::from_gray(34)),
);
}
fn kd_button(label: &str, primary: bool) -> egui::Button<'_> {
let text = RichText::new(label)
.monospace()
.size(if primary { 16.0 } else { 11.0 })
.strong()
.color(if primary {
Color32::BLACK
} else {
Color32::from_gray(220)
});
egui::Button::new(text)
.fill(if primary {
Color32::from_gray(232)
} else {
kd_surface()
})
.stroke(Stroke::new(
1.0,
if primary {
Color32::WHITE
} else {
Color32::from_gray(74)
},
))
.rounding(Rounding::same(1.0))
.min_size(Vec2::new(
if primary { 0.0 } else { 112.0 },
if primary { 52.0 } else { 32.0 },
))
}
fn draw_corner_brackets(painter: &egui::Painter, rect: Rect, len: f32, color: Color32) {
let stroke = Stroke::new(1.0, color);
let corners = [
(rect.left_top(), egui::vec2(1.0, 0.0), egui::vec2(0.0, 1.0)),
(
rect.right_top(),
egui::vec2(-1.0, 0.0),
egui::vec2(0.0, 1.0),
),
(
rect.left_bottom(),
egui::vec2(1.0, 0.0),
egui::vec2(0.0, -1.0),
),
(
rect.right_bottom(),
egui::vec2(-1.0, 0.0),
egui::vec2(0.0, -1.0),
),
];
for (corner, x_dir, y_dir) in corners {
painter.line_segment([corner, corner + x_dir * len], stroke);
painter.line_segment([corner, corner + y_dir * len], stroke);
}
}
fn draw_crosshair(painter: &egui::Painter, center: Pos2, radius: f32, color: Color32) {
let stroke = Stroke::new(1.0, color);
painter.circle_stroke(center, radius, stroke);
painter.circle_stroke(center, radius * 0.52, stroke);
painter.line_segment(
[
center + egui::vec2(-radius, 0.0),
center + egui::vec2(radius, 0.0),
],
stroke,
);
painter.line_segment(
[
center + egui::vec2(0.0, -radius),
center + egui::vec2(0.0, radius),
],
stroke,
);
}
fn draw_trefoil_mark(painter: &egui::Painter, center: Pos2, scale: f32, phase: f32) {
let mut points = Vec::with_capacity(181);
for step in 0..=180 {
let t = step as f32 / 180.0 * std::f32::consts::TAU + phase * 0.08;
let x = (t.sin() + 2.0 * (2.0 * t).sin()) * scale * 0.42;
let y = (t.cos() - 2.0 * (2.0 * t).cos()) * scale * 0.42;
points.push(center + egui::vec2(x, y));
}
painter.add(Shape::line(
points,
Stroke::new(2.0, Color32::from_gray(214)),
));
painter.text(
center,
Align2::CENTER_CENTER,
"C",
FontId::proportional(24.0),
Color32::WHITE,
);
}
fn draw_status_pill(painter: &egui::Painter, pos: Pos2, label: &str) {
let rect = Rect::from_min_size(pos, Vec2::new(220.0, 34.0));
painter.rect_filled(rect, Rounding::same(1.0), kd_surface());
painter.rect_stroke(
rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(82)),
);
painter.circle_filled(rect.left_center() + egui::vec2(16.0, 0.0), 4.0, kd_green());
painter.text(
rect.left_center() + egui::vec2(30.0, 0.0),
Align2::LEFT_CENTER,
label,
FontId::monospace(11.0),
Color32::from_gray(218),
);
}
fn tech_note(ui: &mut egui::Ui, tag: &str, text: &str) {
egui::Frame::none()
.fill(Color32::from_rgb(4, 4, 4))
.stroke(Stroke::new(1.0, Color32::from_gray(38)))
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {
ui.label(
RichText::new(tag)
.monospace()
.size(10.0)
.color(Color32::from_gray(96)),
);
ui.label(
RichText::new(text)
.monospace()
.size(11.0)
.color(Color32::from_gray(178)),
);
});
}
fn mode_chip(
ui: &mut egui::Ui,
title: &str,
description: &str,
selected: bool,
phase: f32,
) -> egui::Response {
let (rect, response) =
ui.allocate_exact_size(Vec2::new(ui.available_width(), 72.0), Sense::click());
let glow = ((phase * 2.7).sin() * 0.5 + 0.5) * 60.0;
ui.painter().rect_filled(
rect,
Rounding::same(1.0),
if selected {
Color32::from_rgb(18, 18, 18)
} else {
kd_surface()
},
);
ui.painter().rect_stroke(
rect,
Rounding::ZERO,
Stroke::new(
if selected { 1.5 } else { 1.0 },
if selected {
Color32::from_rgba_premultiplied(255, 255, 255, (120.0 + glow) as u8)
} else {
Color32::from_gray(46)
},
),
);
if selected {
draw_outer_glow(ui.painter(), rect, 2, 7.0, 22);
}
ui.painter().text(
rect.left_top() + egui::vec2(18.0, 12.0),
Align2::LEFT_TOP,
title.to_ascii_uppercase(),
FontId::monospace(15.0),
Color32::from_gray(236),
);
ui.painter().text(
rect.left_top() + egui::vec2(18.0, 40.0),
Align2::LEFT_TOP,
description.to_ascii_uppercase(),
FontId::monospace(10.0),
Color32::from_gray(132),
);
ui.painter().text(
rect.right_center() + egui::vec2(-18.0, 0.0),
Align2::RIGHT_CENTER,
if selected { "ACTIVE" } else { "STANDBY" },
FontId::monospace(10.0),
if selected {
kd_green()
} else {
Color32::from_gray(82)
},
);
response
}
fn field_label(ui: &mut egui::Ui, label: &str) {
ui.label(
RichText::new(label)
.monospace()
.size(11.0)
.color(Color32::from_gray(166)),
);
}
fn format_controls(
ui: &mut egui::Ui,
mode: ConversionMode,
audio_format: &mut AudioFormat,
video_format: &mut VideoFormat,
quality: &mut QualityPreset,
disabled: bool,
) -> bool {
let mut changed = false;
match mode {
ConversionMode::ExtractAudio | ConversionMode::TranscodeAudio => {
field_label(ui, "AUDIO OUTPUT FORMAT");
ui.horizontal_wrapped(|ui| {
for candidate in AudioFormat::ALL {
ui.add_enabled_ui(!disabled, |ui| {
if ui
.selectable_value(audio_format, candidate, candidate.label())
.changed()
{
changed = true;
}
});
}
});
}
ConversionMode::TranscodeVideo => {
field_label(ui, "VIDEO OUTPUT FORMAT");
ui.horizontal_wrapped(|ui| {
for candidate in VideoFormat::ALL {
ui.add_enabled_ui(!disabled, |ui| {
if ui
.selectable_value(video_format, candidate, candidate.label())
.changed()
{
changed = true;
}
});
}
});
}
}
ui.add_space(8.0);
field_label(ui, "QUALITY PROFILE");
ui.horizontal_wrapped(|ui| {
for candidate in QualityPreset::ALL {
ui.add_enabled_ui(!disabled, |ui| {
if ui
.selectable_value(
quality,
candidate,
format!("{} // {}", candidate.label(), candidate.description()),
)
.changed()
{
changed = true;
}
});
}
});
changed
}
fn media_preview_card(ui: &mut egui::Ui, media_path: &str, mode: ConversionMode, phase: f32) {
let pulse = ((phase * 3.4).sin() * 0.5 + 0.5) * 44.0;
let (title, detail, badge, badge_color, stroke_color, icon) =
media_preview_state(media_path, mode, pulse);
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 104.0), Sense::hover());
ui.painter()
.rect_filled(rect, Rounding::same(1.0), kd_surface());
ui.painter()
.rect_stroke(rect, Rounding::ZERO, Stroke::new(1.0, stroke_color));
if source_file_ready(media_path, mode) {
draw_outer_glow(ui.painter(), rect, 2, 7.0, 20);
}
draw_corner_brackets(ui.painter(), rect.shrink(2.0), 14.0, Color32::from_gray(78));
let icon_rect = Rect::from_min_size(
rect.left_top() + egui::vec2(16.0, 22.0),
Vec2::new(58.0, 58.0),
);
ui.painter().rect_stroke(
icon_rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(112)),
);
ui.painter().line_segment(
[
icon_rect.left_top() + egui::vec2(10.0, 14.0),
icon_rect.right_top() + egui::vec2(-10.0, 14.0),
],
Stroke::new(1.0, Color32::from_gray(48)),
);
ui.painter().text(
icon_rect.center(),
Align2::CENTER_CENTER,
icon,
FontId::monospace(13.0),
Color32::from_gray(230),
);
ui.painter().text(
rect.left_top() + egui::vec2(92.0, 20.0),
Align2::LEFT_TOP,
title.to_ascii_uppercase(),
FontId::proportional(18.0),
Color32::from_gray(238),
);
ui.painter().text(
rect.left_top() + egui::vec2(92.0, 50.0),
Align2::LEFT_TOP,
detail.to_ascii_uppercase(),
FontId::monospace(11.0),
Color32::from_gray(142),
);
ui.painter().text(
rect.right_top() + egui::vec2(-18.0, 20.0),
Align2::RIGHT_TOP,
badge,
FontId::monospace(10.0),
badge_color,
);
}
fn media_preview_state(
media_path: &str,
mode: ConversionMode,
pulse: f32,
) -> (String, String, &'static str, Color32, Color32, &'static str) {
if media_path.is_empty() {
return (
"Choose a media source".to_owned(),
"Open Media Vault, paste a path, or drag an audio/video file here.".to_owned(),
"WAITING",
Color32::from_gray(140),
Color32::from_gray(42),
"---",
);
}
let path = Path::new(media_path);
if !supported_media_extension(path) {
return (
file_name_or_path(path),
"Unsupported extension for the media source list.".to_owned(),
"CHECK EXT",
Color32::from_gray(210),
Color32::from_gray(112),
"FILE",
);
}
if mode.expects_video_input() && !supported_video_extension(path) {
return (
file_name_or_path(path),
"The selected route expects a video source.".to_owned(),
"WRONG ROUTE",
Color32::from_gray(210),
Color32::from_gray(112),
media_kind_label(path),
);
}
if matches!(mode, ConversionMode::TranscodeAudio) && !supported_audio_extension(path) {
return (
file_name_or_path(path),
"The selected route expects an audio source.".to_owned(),
"WRONG ROUTE",
Color32::from_gray(210),
Color32::from_gray(112),
media_kind_label(path),
);
}
match fs::metadata(path) {
Ok(metadata) if metadata.is_file() => {
let name = file_name_or_path(path);
let folder = path
.parent()
.map(format_path_display)
.unwrap_or_else(|| "CURRENT FOLDER".to_owned());
(
name,
format!("{} // {}", human_size(metadata.len()), folder),
"SOURCE READY",
kd_green(),
Color32::from_rgba_premultiplied(255, 255, 255, (96.0 + pulse) as u8),
media_kind_label(path),
)
}
Ok(_) => (
file_name_or_path(path),
"That path exists, but it is not a file.".to_owned(),
"NOT FILE",
Color32::from_gray(210),
Color32::from_gray(112),
media_kind_label(path),
),
Err(_) => (
file_name_or_path(path),
"File not found yet. Pick an existing media file.".to_owned(),
"MISSING",
Color32::from_gray(185),
Color32::from_gray(82),
media_kind_label(path),
),
}
}
fn output_preview_card(
ui: &mut egui::Ui,
output_path: &str,
mode: ConversionMode,
audio_format: AudioFormat,
video_format: VideoFormat,
overwrite: bool,
phase: f32,
) {
let pulse = ((phase * 3.0).sin() * 0.5 + 0.5) * 44.0;
let expected = mode.output_extension(audio_format, video_format);
let (title, detail, badge, badge_color, stroke_color) =
output_preview_state(output_path, expected, overwrite, pulse);
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 94.0), Sense::hover());
ui.painter()
.rect_filled(rect, Rounding::same(1.0), kd_surface());
ui.painter()
.rect_stroke(rect, Rounding::ZERO, Stroke::new(1.0, stroke_color));
if output_path_ready(output_path, expected, overwrite) {
draw_outer_glow(ui.painter(), rect, 1, 6.0, 16);
}
draw_corner_brackets(ui.painter(), rect.shrink(2.0), 14.0, Color32::from_gray(78));
let icon_rect = Rect::from_min_size(
rect.left_top() + egui::vec2(16.0, 18.0),
Vec2::new(52.0, 52.0),
);
ui.painter().rect_stroke(
icon_rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(112)),
);
ui.painter().text(
icon_rect.center(),
Align2::CENTER_CENTER,
"OUT",
FontId::monospace(12.0),
Color32::from_gray(230),
);
ui.painter().text(
rect.left_top() + egui::vec2(84.0, 17.0),
Align2::LEFT_TOP,
title.to_ascii_uppercase(),
FontId::proportional(17.0),
Color32::from_gray(238),
);
ui.painter().text(
rect.left_top() + egui::vec2(84.0, 47.0),
Align2::LEFT_TOP,
detail.to_ascii_uppercase(),
FontId::monospace(11.0),
Color32::from_gray(142),
);
ui.painter().text(
rect.right_top() + egui::vec2(-18.0, 18.0),
Align2::RIGHT_TOP,
badge,
FontId::monospace(10.0),
badge_color,
);
}
fn output_preview_state(
output_path: &str,
expected_extension: &str,
overwrite: bool,
pulse: f32,
) -> (String, String, &'static str, Color32, Color32) {
if output_path.is_empty() {
return (
"Choose an output path".to_owned(),
format!("Expected .{expected_extension} for the current route."),
"WAITING",
Color32::from_gray(140),
Color32::from_gray(42),
);
}
let path = Path::new(output_path);
if path_extension_lower(path).as_deref() != Some(expected_extension) {
return (
file_name_or_path(path),
format!("Output extension should be .{expected_extension}."),
"CHECK EXT",
Color32::from_gray(210),
Color32::from_gray(112),
);
}
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() && !parent.is_dir() {
return (
file_name_or_path(path),
format!("Folder does not exist: {}", format_path_display(parent)),
"BAD PATH",
Color32::from_gray(210),
Color32::from_gray(112),
);
}
}
if path.exists() {
return (
file_name_or_path(path),
"Output already exists.".to_owned(),
if overwrite { "OVERWRITE" } else { "EXISTS" },
if overwrite {
kd_green()
} else {
Color32::from_gray(210)
},
if overwrite {
Color32::from_rgba_premultiplied(255, 255, 255, (90.0 + pulse) as u8)
} else {
Color32::from_gray(112)
},
);
}
(
file_name_or_path(path),
path.parent()
.map(format_path_display)
.unwrap_or_else(|| "CURRENT FOLDER".to_owned()),
"READY",
kd_green(),
Color32::from_rgba_premultiplied(255, 255, 255, (88.0 + pulse) as u8),
)
}
fn readiness_panel(
ui: &mut egui::Ui,
source_ready: bool,
output_ready: bool,
mode: ConversionMode,
) {
egui::Frame::none()
.fill(Color32::from_rgb(4, 4, 4))
.stroke(Stroke::new(1.0, Color32::from_gray(38)))
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
ui.label(
RichText::new("PRE-FLIGHT CHECK")
.monospace()
.size(11.0)
.strong()
.color(Color32::from_gray(172)),
);
ui.add_space(5.0);
readiness_row(ui, source_ready, mode.source_label());
readiness_row(ui, output_ready, "OUTPUT PATH READY");
readiness_row(ui, true, "FFMPEG ROUTE SELECTED");
readiness_row(ui, false, "FINAL ROUTE CHECK REQUIRED ON SUBMIT");
});
}
fn readiness_row(ui: &mut egui::Ui, ready: bool, label: &str) {
ui.horizontal(|ui| {
let (rect, _) = ui.allocate_exact_size(Vec2::splat(12.0), Sense::hover());
ui.painter().circle_filled(
rect.center(),
4.5,
if ready {
kd_green()
} else {
Color32::from_gray(68)
},
);
ui.label(RichText::new(label).monospace().size(11.0).color(if ready {
Color32::from_gray(214)
} else {
Color32::from_gray(104)
}));
});
}
fn output_write_box(ui: &mut egui::Ui, output_path: &str, overwrite: &mut bool, disabled: bool) {
egui::Frame::none()
.fill(Color32::from_rgb(4, 4, 4))
.stroke(Stroke::new(1.0, Color32::from_gray(48)))
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
field_label(ui, "OUTPUT WRITE POLICY");
let exists = Path::new(output_path).exists();
let message = if exists {
"Allow Caery to replace the existing output file."
} else {
"Output path is new. Enable only if you expect to replace existing files."
};
ui.add_enabled(
!disabled,
egui::Checkbox::new(
overwrite,
RichText::new(message.to_ascii_uppercase())
.monospace()
.size(11.0)
.color(Color32::from_gray(210)),
),
);
});
}
fn final_warning_card(ui: &mut egui::Ui, phase: f32) {
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 58.0), Sense::hover());
let pulse = ((phase * 7.0).sin() * 0.5 + 0.5) * 70.0;
ui.painter()
.rect_filled(rect, Rounding::same(1.0), Color32::from_rgb(10, 10, 10));
ui.painter().rect_stroke(
rect,
Rounding::ZERO,
Stroke::new(
1.0,
Color32::from_rgba_premultiplied(255, 255, 255, (95.0 + pulse) as u8),
),
);
draw_corner_brackets(
ui.painter(),
rect.shrink(2.0),
13.0,
Color32::from_gray(100),
);
ui.painter().text(
rect.center(),
Align2::CENTER_CENTER,
"LAST GATE BEFORE MEDIA FILE WRITE",
FontId::monospace(13.0),
Color32::from_gray(236),
);
}
fn animated_progress(ui: &mut egui::Ui, current: f64, total: f64, phase: f32) {
let percent = if total <= 0.0 { 0.0 } else { current / total }.clamp(0.0, 1.0) as f32;
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 38.0), Sense::hover());
draw_progress_bar(
ui.painter(),
rect,
percent,
phase,
&duration_progress_label(current, total),
);
}
fn indeterminate_progress(ui: &mut egui::Ui, phase: f32) {
let value = ((phase * 2.4).sin() * 0.5 + 0.5).clamp(0.04, 0.96);
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 38.0), Sense::hover());
draw_progress_bar(ui.painter(), rect, value, phase, "WORKING // FFMPEG ACTIVE");
}
fn draw_progress_bar(painter: &egui::Painter, rect: Rect, value: f32, phase: f32, label: &str) {
painter.rect_filled(rect, Rounding::same(1.0), kd_surface());
painter.rect_stroke(
rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(58)),
);
let fill_rect = Rect::from_min_max(
rect.left_top(),
Pos2::new(rect.left() + rect.width() * value, rect.bottom()),
);
painter.rect_filled(fill_rect, Rounding::same(1.0), Color32::from_gray(210));
let shimmer_x = rect.left() + rect.width() * ((phase * 0.75).fract());
painter.line_segment(
[
Pos2::new(shimmer_x, rect.top()),
Pos2::new(shimmer_x + 34.0, rect.bottom()),
],
Stroke::new(2.0, Color32::from_rgba_premultiplied(255, 255, 255, 110)),
);
painter.text(
rect.center(),
Align2::CENTER_CENTER,
label.to_ascii_uppercase(),
FontId::monospace(11.0),
if value > 0.48 {
Color32::BLACK
} else {
Color32::from_gray(210)
},
);
}
fn log_panel(ui: &mut egui::Ui, logs: &[String], compact: bool) {
field_label(ui, "LIVE TELEMETRY");
egui::Frame::none()
.fill(Color32::from_rgb(2, 2, 2))
.stroke(Stroke::new(1.0, Color32::from_gray(34)))
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
egui::ScrollArea::vertical()
.max_height(if compact { 105.0 } else { 150.0 })
.stick_to_bottom(true)
.show(ui, |ui| {
if logs.is_empty() {
ui.label(
RichText::new("TELEMETRY STREAM IDLE")
.monospace()
.size(11.0)
.color(Color32::from_gray(82)),
);
}
for line in logs {
ui.label(
RichText::new(format!("> {line}"))
.monospace()
.size(11.0)
.color(Color32::from_gray(176)),
);
}
});
});
}
fn warning_text(ui: &mut egui::Ui, text: &str) {
ui.label(
RichText::new(text)
.monospace()
.size(11.0)
.color(Color32::from_gray(192)),
);
}
fn media_browser_contents(
ui: &mut egui::Ui,
state: &MediaBrowserState,
phase: f32,
) -> BrowserAction {
let mut action = BrowserAction::None;
panel_heading(ui, "MEDIA VAULT", "SELECT SOURCE MEDIA");
ui.add_space(10.0);
let path_text = format_path_display(&state.current_dir);
tech_note(ui, "CURRENT DIRECTORY", &path_text);
ui.add_space(8.0);
ui.horizontal(|ui| {
if ui
.add_enabled(
state.current_dir.parent().is_some(),
kd_button("PARENT", false),
)
.clicked()
{
action = BrowserAction::Parent;
}
if ui.add(kd_button("REFRESH", false)).clicked() {
action = BrowserAction::Refresh;
}
if ui.add(kd_button("CLOSE", false)).clicked() {
action = BrowserAction::Close;
}
});
ui.add_space(10.0);
if let Some(error) = &state.error {
warning_text(ui, &format!("DIRECTORY READ FAILED // {error}"));
return action;
}
egui::Frame::none()
.fill(Color32::from_rgb(2, 2, 2))
.stroke(Stroke::new(1.0, Color32::from_gray(38)))
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {
egui::ScrollArea::vertical()
.max_height(350.0)
.auto_shrink([false, false])
.show(ui, |ui| {
if state.entries.is_empty() {
ui.label(
RichText::new("NO FILES IN DIRECTORY")
.monospace()
.size(11.0)
.color(Color32::from_gray(96)),
);
}
for entry in &state.entries {
let response = browser_entry_row(ui, entry, phase);
if response.clicked() {
match entry.kind {
BrowserEntryKind::Directory => {
action = BrowserAction::OpenDirectory(entry.path.clone());
}
BrowserEntryKind::Media => {
action = BrowserAction::SelectMedia(entry.path.clone());
}
BrowserEntryKind::Other => {}
}
}
ui.add_space(6.0);
}
});
});
action
}
fn browser_entry_row(ui: &mut egui::Ui, entry: &BrowserEntry, phase: f32) -> egui::Response {
let clickable = entry.kind != BrowserEntryKind::Other;
let (rect, response) = ui.allocate_exact_size(
Vec2::new(ui.available_width(), 42.0),
if clickable {
Sense::click()
} else {
Sense::hover()
},
);
let fill = if response.hovered() && clickable {
Color32::from_rgb(20, 20, 20)
} else {
kd_surface()
};
ui.painter().rect_filled(rect, Rounding::same(1.0), fill);
ui.painter().rect_stroke(
rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(36)),
);
let kind = match entry.kind {
BrowserEntryKind::Directory => "DIR",
BrowserEntryKind::Media => media_kind_label(&entry.path),
BrowserEntryKind::Other => "---",
};
let color = match entry.kind {
BrowserEntryKind::Directory => Color32::from_gray(215),
BrowserEntryKind::Media => kd_green(),
BrowserEntryKind::Other => Color32::from_gray(74),
};
let pulse = ((phase * 4.0).sin() * 0.5 + 0.5) * 35.0;
ui.painter().text(
rect.left_center() + egui::vec2(12.0, 0.0),
Align2::LEFT_CENTER,
kind,
FontId::monospace(10.0),
if entry.kind == BrowserEntryKind::Media {
Color32::from_rgba_premultiplied(0, 255, 136, (160.0 + pulse) as u8)
} else {
color
},
);
ui.painter().text(
rect.left_center() + egui::vec2(72.0, 0.0),
Align2::LEFT_CENTER,
entry.name.to_ascii_uppercase(),
FontId::monospace(11.0),
color,
);
response
}
fn source_file_ready(media_path: &str, mode: ConversionMode) -> bool {
let media_path = media_path.trim();
if media_path.is_empty() {
return false;
}
let path = Path::new(media_path);
let route_matches = if mode.expects_video_input() {
supported_video_extension(path)
} else {
supported_audio_extension(path)
};
route_matches
&& fs::metadata(path)
.map(|metadata| metadata.is_file())
.unwrap_or(false)
}
fn output_path_ready(output_path: &str, expected_extension: &str, overwrite: bool) -> bool {
let output_path = output_path.trim();
if output_path.is_empty() {
return false;
}
let path = Path::new(output_path);
if path_extension_lower(path).as_deref() != Some(expected_extension) {
return false;
}
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() && !parent.is_dir() {
return false;
}
}
!path.exists() || overwrite
}
fn file_name_or_path(path: &Path) -> String {
path.file_name()
.and_then(|name| name.to_str())
.map(str::to_owned)
.unwrap_or_else(|| path.display().to_string())
}
fn format_path_display(path: &Path) -> String {
let display = path.display().to_string();
if let Some(home) = env::var_os("HOME").map(PathBuf::from) {
if let Ok(stripped) = path.strip_prefix(&home) {
if stripped.as_os_str().is_empty() {
return "~".to_owned();
}
return format!("~/{}", stripped.display());
}
}
display
}
fn initial_browser_directory(current_media_path: &str) -> PathBuf {
let current_media_path = current_media_path.trim();
if !current_media_path.is_empty() {
let path = Path::new(current_media_path);
if path.is_dir() {
return path.to_path_buf();
}
if let Some(parent) = path.parent().filter(|parent| parent.is_dir()) {
return parent.to_path_buf();
}
}
if let Some(home) = env::var_os("HOME")
.map(PathBuf::from)
.filter(|path| path.is_dir())
{
return home;
}
env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))
}
fn scan_browser_directory(path: &Path) -> std::io::Result<Vec<BrowserEntry>> {
let mut entries = Vec::new();
for entry in fs::read_dir(path)? {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue,
};
let entry_path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();
let kind = match entry.file_type() {
Ok(file_type) if file_type.is_dir() => BrowserEntryKind::Directory,
Ok(file_type) if file_type.is_file() && supported_media_extension(&entry_path) => {
BrowserEntryKind::Media
}
Ok(file_type) if file_type.is_file() => BrowserEntryKind::Other,
_ => continue,
};
entries.push(BrowserEntry {
name,
path: entry_path,
kind,
});
}
entries.sort_by(|left, right| {
left.kind.cmp(&right.kind).then_with(|| {
left.name
.to_ascii_lowercase()
.cmp(&right.name.to_ascii_lowercase())
})
});
Ok(entries)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn source_ready_respects_route_media_type() {
let dir = tempdir().expect("temp dir");
let video = dir.path().join("clip.mp4");
let audio = dir.path().join("song.wav");
fs::write(&video, b"fake video").expect("write video");
fs::write(&audio, b"fake audio").expect("write audio");
assert!(source_file_ready(
&video.display().to_string(),
ConversionMode::ExtractAudio
));
assert!(!source_file_ready(
&audio.display().to_string(),
ConversionMode::ExtractAudio
));
assert!(source_file_ready(
&audio.display().to_string(),
ConversionMode::TranscodeAudio
));
}
#[test]
fn output_ready_requires_matching_extension() {
let dir = tempdir().expect("temp dir");
let mp3 = dir.path().join("out.mp3");
let wav = dir.path().join("out.wav");
assert!(output_path_ready(&mp3.display().to_string(), "mp3", false));
assert!(!output_path_ready(&wav.display().to_string(), "mp3", false));
}
#[test]
fn scan_browser_directory_prioritizes_folders_then_media() {
let dir = tempdir().expect("temp dir");
fs::create_dir(dir.path().join("z_folder")).expect("create folder");
fs::write(dir.path().join("clip.mp4"), b"fake video").expect("write video");
fs::write(dir.path().join("notes.txt"), b"notes").expect("write notes");
let entries = scan_browser_directory(dir.path()).expect("scan dir");
let kinds = entries.iter().map(|entry| entry.kind).collect::<Vec<_>>();
assert_eq!(
kinds,
vec![
BrowserEntryKind::Directory,
BrowserEntryKind::Media,
BrowserEntryKind::Other
]
);
}
}