readmdict 0.1.0

A Rust implementation for reading MDict dictionary files (.mdx format)
Documentation
use std::path::Path;
use std::process::{Command, Stdio};
use std::env;
use std::fs::File;
use std::io::Write;
use std::process;
use rust_readmdict::readmdict::Mdd;

fn main() {
    let args: Vec<String> = env::args().collect();
    
    // Parse command line arguments
    if args.len() < 2 {
        eprintln!("Usage: cargo run --example play_audio <mdd_file>");
        eprintln!("Play audio files from MDD files");
        process::exit(1);
    }
    
    let mdd_file = &args[1];

    // Check if MDD file exists
    if !Path::new(mdd_file).exists() {
        eprintln!("Error: MDD file '{}' not found", mdd_file);
        process::exit(1);
    }

    // Load and play audio from MDD file
    match load_and_play_audio(mdd_file) {
        Ok(_) => {},
        Err(e) => {
            eprintln!("Error: {}", e);
            if e.to_string().to_lowercase().contains("lzo") {
                eprintln!("\nThis error is likely related to missing LZO compression support.");
                eprintln!("Please install the LZO library.");
            }
            process::exit(1);
        }
    }
}

fn load_and_play_audio(mdd_file: &str) -> Result<(), Box<dyn std::error::Error>> {
    println!("Loading MDD file: {}", mdd_file);
    
    let mdd = Mdd::new(mdd_file, None)?;
    
    // Find all audio files
    let mut audio_files = Vec::new();
    let mdd_items = mdd.items()?;
    
    for (filename, content) in mdd_items {
        let filename_str = String::from_utf8_lossy(&filename);
        let filename_lower = filename_str.to_lowercase();
        
        if filename_lower.ends_with(".mp3") || 
           filename_lower.ends_with(".wav") || 
           filename_lower.ends_with(".ogg") || 
           filename_lower.ends_with(".m4a") {
            audio_files.push((filename_str.to_string(), content));
        }
    }

    println!("Found {} audio files", audio_files.len());

    if audio_files.is_empty() {
        println!("No audio files found in the MDD file.");
        return Ok(());
    }

    // Play first 10 audio files automatically
    let files_to_play = &audio_files[..audio_files.len().min(10)];
    println!("\nPlaying {} audio files sequentially...", files_to_play.len());

    for (i, (filename, content)) in files_to_play.iter().enumerate() {
        println!("\n[{}/{}] {} ({} bytes)", i + 1, files_to_play.len(), filename, content.len());
        
        match play_audio_file(content, filename) {
            Ok(_) => println!("Successfully played {}", filename),
            Err(e) => {
                eprintln!("Failed to play {}: {}", filename, e);
            }
        }
    }

    println!("\nFinished playing all audio files.");
    Ok(())
}

fn play_audio_file(audio_data: &[u8], filename: &str) -> Result<(), String> {
    use std::env;
    
    // Create a temporary file with appropriate extension
    let temp_path = create_temp_audio_file(audio_data, filename)?;
    
    println!("Playing {}...", filename);
    
    // Try different audio players based on the system
    let result = match env::consts::OS {
        "macos" => {
            // macOS - use afplay
            Command::new("afplay")
                .arg(&temp_path)
                .stdout(Stdio::null())
                .stderr(Stdio::null())
                .status()
                .map_err(|e| format!("Failed to run afplay: {}", e))
        },
        "linux" => {
            // Linux - try common audio players in order of preference
            let players = ["mpg123", "mpv", "vlc", "mplayer"];
            let mut last_error = None;
            
            for player in &players {
                match Command::new(player)
                    .arg(&temp_path)
                    .stdout(Stdio::null())
                    .stderr(Stdio::null())
                    .status()
                {
                    Ok(status) if status.success() => {
                        last_error = None;
                        break;
                    }
                    Ok(_) => {
                        last_error = Some(format!("Player {} exited with non-zero status", player));
                    }
                    Err(_) => {
                        last_error = Some(format!("Player {} not found", player));
                    }
                }
            }
            
            match last_error {
                None => return Ok(()),
                Some(_) => return Err(format!("No suitable audio player found. Tried: {}", players.join(", ")))
            }
        },
        "windows" => {
            // Windows - use start command
            Command::new("cmd")
                .args(&["/C", "start", ""])
                .arg(&temp_path)
                .stdout(Stdio::null())
                .stderr(Stdio::null())
                .status()
                .map_err(|e| format!("Failed to start audio player: {}", e))
        },
        _ => {
            Err(format!("Unsupported platform: {}", env::consts::OS))
        }
    };
    
    // Clean up temporary file
    let _ = std::fs::remove_file(&temp_path);
    
    result.map(|_| ()).map_err(|e| e)
}

fn create_temp_audio_file(audio_data: &[u8], filename: &str) -> Result<String, String> {
    use std::env;
    
    // Determine file extension from filename
    let extension = Path::new(filename)
        .extension()
        .and_then(|ext| ext.to_str())
        .unwrap_or("mp3");
    
    // Create temporary file with appropriate extension
    let temp_dir = env::temp_dir();
    let temp_filename = format!("rust_audio_{}.{}", 
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos(), 
        extension);
    
    let temp_path = temp_dir.join(temp_filename);
    
    // Write audio data to temporary file
    let mut file = File::create(&temp_path)
        .map_err(|e| format!("Failed to create temporary file: {}", e))?;
    
    file.write_all(audio_data)
        .map_err(|e| format!("Failed to write audio data: {}", e))?;
    
    Ok(temp_path.to_string_lossy().to_string())
}