koan-cli 0.29.0

CLI entry point for koan — bit-perfect music player.
use std::path::Path;

use koan_core::audio;
use koan_core::audio::buffer;
use owo_colors::OwoColorize;

use super::format_time;

pub fn cmd_probe(path: &Path) {
    if !path.exists() {
        eprintln!("{} {}", "not found:".red().bold(), path.display());
        std::process::exit(1);
    }

    match buffer::probe_file(path) {
        Ok(info) => {
            println!("{} {}", "file:".cyan(), path.display());
            println!("{} {}", "codec:".cyan(), info.codec.yellow());
            println!("{} {} Hz", "sample rate:".cyan(), info.sample_rate);
            if let Some(bd) = info.bit_depth {
                println!("{} {}", "bit depth:".cyan(), bd);
            }
            if let Some(kbps) = info.bitrate_kbps {
                println!("{} {} kbps", "bitrate:".cyan(), kbps);
            }
            println!("{} {}", "channels:".cyan(), info.channels);
            println!(
                "{} {} {}",
                "duration:".cyan(),
                format_time(info.duration_ms),
                format!("({}ms)", info.duration_ms).dimmed()
            );
        }
        Err(e) => {
            eprintln!("{} {}", "probe failed:".red().bold(), e);
            std::process::exit(1);
        }
    }
}

pub fn cmd_devices() {
    match audio::list_output_devices() {
        Ok(devices) => {
            let default = audio::default_output_device().ok();
            let default_name = default.as_ref().map(|d| d.name.as_str());
            for dev in &devices {
                let is_default = Some(dev.name.as_str()) == default_name;
                let marker = if is_default {
                    " *".yellow().bold().to_string()
                } else {
                    String::new()
                };
                println!(
                    "{} {}{}",
                    format!("[{}]", dev.platform_id).dimmed(),
                    if is_default {
                        dev.name.bold().to_string()
                    } else {
                        dev.name.to_string()
                    },
                    marker,
                );
                if !dev.sample_rates.is_empty() {
                    let rates: Vec<String> = dev
                        .sample_rates
                        .iter()
                        .map(|r| format!("{}Hz", *r as u32))
                        .collect();
                    println!("  {} {}", "rates:".dimmed(), rates.join(", ").dimmed());
                }
            }
        }
        Err(e) => {
            eprintln!("{} {}", "error:".red().bold(), e);
            std::process::exit(1);
        }
    }
}