use crate::crypto::{PrivateKey, decrypt_asymmetric, decrypt_symmetric};
use crate::format::{EmbeddedData, Payload};
use crate::stego::traits::{ChannelMode, EmbedOptions};
use crate::stego::{LsbSteganography, MetadataSteganography, StegoMethod};
use anyhow::{Result, anyhow};
use clap::Args;
use std::path::PathBuf;
use std::process::Command;
#[derive(Args)]
pub struct PlayArgs {
pub input: PathBuf,
#[arg(long, conflicts_with = "key")]
pub passphrase: Option<String>,
#[arg(long, conflicts_with = "passphrase")]
pub key: Option<PathBuf>,
#[arg(long = "extract-to")]
pub extract_to: Option<PathBuf>,
#[arg(long, default_value = "afplay")]
pub player: String,
#[arg(long, default_value = "1")]
pub bits: u8,
#[arg(long, value_enum, default_value = "both")]
pub channels: ChannelMode,
}
pub fn run(args: PlayArgs) -> Result<()> {
if !args.input.exists() {
return Err(anyhow!(
"Input file does not exist: {}",
args.input.display()
));
}
let data = try_extract(&args)?;
let embedded = EmbeddedData::from_bytes(&data)?;
let flags = &embedded.header.flags;
if !flags.has_audio {
return Err(anyhow!("No audio content is embedded in this file"));
}
let payload_bytes = if flags.symmetric_encryption {
let passphrase = args
.passphrase
.as_ref()
.ok_or_else(|| anyhow!("Audio is encrypted. Use --passphrase to decrypt."))?;
decrypt_symmetric(&embedded.payload, passphrase)?
} else if flags.asymmetric_encryption {
let key_path = args
.key
.as_ref()
.ok_or_else(|| anyhow!("Audio is encrypted. Use --key to decrypt."))?;
let private_key = PrivateKey::load(key_path)?;
decrypt_asymmetric(&embedded.payload, &private_key)?
} else {
embedded.payload.clone()
};
let payload = Payload::from_bytes(&payload_bytes)?;
let audio_data = payload
.audio
.ok_or_else(|| anyhow!("No audio content found in payload"))?;
if let Some(ref output_path) = args.extract_to {
crate::audio::decompress_audio(&audio_data, output_path)?;
eprintln!("Extracted audio to: {}", output_path.display());
} else {
let temp_dir = tempfile::tempdir()?;
let temp_path = temp_dir.path().join("extracted.wav");
crate::audio::decompress_audio(&audio_data, &temp_path)?;
let player = find_player(&args.player)?;
eprintln!("Playing with: {}", player);
let status = Command::new(&player).arg(&temp_path).status()?;
if !status.success() {
return Err(anyhow!("Player exited with error"));
}
}
Ok(())
}
fn try_extract(args: &PlayArgs) -> Result<Vec<u8>> {
let metadata_stego = MetadataSteganography::new();
if let Ok(data) = metadata_stego.extract(&args.input)
&& data.len() >= 4
&& &data[0..4] == b"ZIMH"
{
return Ok(data);
}
let options = EmbedOptions {
bits_per_sample: args.bits,
channels: args.channels,
};
let lsb_stego = LsbSteganography::new(options);
let data = lsb_stego.extract(&args.input)?;
if data.len() >= 4 && &data[0..4] == b"ZIMH" {
return Ok(data);
}
Err(anyhow!("No valid zimhide data found in file"))
}
fn find_player(preferred: &str) -> Result<String> {
if which::which(preferred).is_ok() {
return Ok(preferred.to_string());
}
let players = ["afplay", "mpv", "ffplay", "aplay", "paplay"];
for player in players {
if which::which(player).is_ok() {
return Ok(player.to_string());
}
}
Err(anyhow!(
"No audio player found. Install mpv, ffplay, or specify --player"
))
}