Opus Explorer - A CLI for Discovering Classical Music
I wanted a simple way to browse classical music from the terminal. The OpenOpus API is free, doesn’t require a key, and has solid metadata on composers and works. So I built a small Rust CLI called Opus Explorer to search composers, list their works, get random suggestions, and keep a local listening log.
The result
What it does
- Search composers –
opus search "Bach"hits the API and prints a table (ID, name, era, birth, death). - List works –
opus works "87"fetches all works for that composer ID and shows title, subtitle, genre, year. - Random –
opus randompicks a composer from the popular list, fetches their works, and suggests one. Useful when you don’t know what to listen to. - Log –
opus log "Brandenburg Concerto No. 3" 5appends an entry tolistening_log.jsonwith work title, date, and a 1–5 rating.
The tables use comfy-table, so output is readable. Errors (API down, no results) are handled with anyhow and surface clear messages.
Using it
cargo build --release
./target/release/opus-explorer search "Bach"
./target/release/opus-explorer works "87"
./target/release/opus-explorer random
./target/release/opus-explorer log "Brandenburg Concerto No. 3" 5
The listening log lives in the project directory as listening_log.json. Each entry has work_title, composer (currently “Unknown” unless you add that later), date, and rating.
Tech stack
Rust 2021, clap (derive) for the CLI, reqwest (blocking) for HTTP, serde / serde_json for API responses, comfy-table for output, chrono for timestamps in the log. The OpenOpus response wraps everything in a status object rather than a plain string, so the models use Option<serde_json::Value> for status to deserialize correctly.
What I learned
The API tripped me up at first because the response wraps everything in a status object rather than a plain string. Once I fixed the models to handle that, the rest was smooth.
For random suggestions, I call the popular-composers endpoint when it works, otherwise I search for a common letter and pick from the results. The log is just a JSON file you append to—no database, no schema. I kept it minimal on purpose so it’s easy to script or extend.
It’s a small project, but it does what I wanted: search composers, browse works, get a random nudge now and then, and keep a simple log of what I’ve listened to. The code is split into client, models, storage, and the CLI, so adding more later is straightforward.