use std::net::IpAddr;
use framesmith::{FileAuthTokenStore, FrameTv};
#[tokio::main]
async fn main() -> Result<(), framesmith::Error> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("Usage: {} <TV_IP> <COMMAND> [ARGS...]", args[0]);
eprintln!("Run with --help for available commands");
std::process::exit(1);
}
let ip: IpAddr = args[1].parse().expect("invalid IP address");
let command = args[2].as_str();
let token_path = std::env::temp_dir().join("framesmith_token.txt");
let tv = FrameTv::connection_builder(ip)
.auth_token_store(FileAuthTokenStore::new(&token_path))
.connect()
.await?;
match command {
"list" => {
let images = tv.get_uploaded_art_images().await?;
if images.is_empty() {
println!("No uploaded images.");
return Ok(());
}
let selected = tv.get_selected_art_image().await.ok();
let selected_id = selected.as_ref().map(|s| s.id());
println!("{:<16} {:>5} x {:<5} {:<8}", "ID", "W", "H", "MATTE");
println!("{}", "-".repeat(60));
for img in &images {
let marker = if selected_id == Some(img.id()) {
" <-- displaying"
} else {
""
};
let matte = img.matte_id().unwrap_or("-");
println!(
"{:<16} {:>5} x {:<5} {:<8}{}",
img.id(),
img.width(),
img.height(),
matte,
marker,
);
}
println!("\n{} image(s) total", images.len());
}
"select" => {
let id = require_arg(&args, 3, "content ID");
tv.select_art_image_by_id(&id).await?;
println!("Now displaying: {id}");
}
"delete" => {
if args.len() < 4 {
eprintln!("Usage: delete <ID> [ID...]");
std::process::exit(1);
}
let ids: Vec<&str> = args[3..].iter().map(|s| s.as_str()).collect();
tv.delete_uploaded_art_images(&ids).await?;
println!("Deleted {} image(s)", ids.len());
}
"favorite" => {
let id = require_arg(&args, 3, "content ID");
tv.set_art_image_favorite(&id, true).await?;
println!("Marked {id} as favorite");
}
"unfavorite" => {
let id = require_arg(&args, 3, "content ID");
tv.set_art_image_favorite(&id, false).await?;
println!("Removed {id} from favorites");
}
"mattes" => {
let mattes = tv.get_available_mattes().await?;
println!("{:<30} TYPE", "ID");
println!("{}", "-".repeat(50));
for matte in &mattes {
println!("{:<30} {}", matte.id(), matte.matte_type());
}
}
"set-matte" => {
let id = require_arg(&args, 3, "content ID");
let matte_id = require_arg(&args, 4, "matte ID");
tv.set_art_image_matte(&id, &matte_id, None).await?;
println!("Applied matte '{matte_id}' to {id}");
}
"filters" => {
let filters = tv.get_available_photo_filters().await?;
println!("{:<20} NAME", "ID");
println!("{}", "-".repeat(40));
for filter in &filters {
println!("{:<20} {}", filter.id(), filter.name());
}
}
"set-filter" => {
let id = require_arg(&args, 3, "content ID");
let filter_id = require_arg(&args, 4, "filter ID");
tv.set_art_image_photo_filter(&id, &filter_id).await?;
println!("Applied filter '{filter_id}' to {id}");
}
"thumbnail" => {
let id = require_arg(&args, 3, "content ID");
let out_path = require_arg(&args, 4, "output path");
let thumbnail = tv.get_art_image_thumbnail(&id).await?;
std::fs::write(&out_path, thumbnail.as_bytes())?;
println!(
"Saved thumbnail to {out_path} ({} bytes)",
thumbnail.as_bytes().len()
);
}
other => {
eprintln!("Unknown command: {other}");
eprintln!(
"Available: list, select, delete, favorite, unfavorite, mattes, set-matte, filters, set-filter, thumbnail"
);
std::process::exit(1);
}
}
Ok(())
}
fn require_arg(args: &[String], index: usize, name: &str) -> String {
args.get(index).cloned().unwrap_or_else(|| {
eprintln!("Missing required argument: <{name}>");
std::process::exit(1);
})
}