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

Opus Explorer demo

What it does

  • Search composersopus search "Bach" hits the API and prints a table (ID, name, era, birth, death).
  • List worksopus works "87" fetches all works for that composer ID and shows title, subtitle, genre, year.
  • Randomopus random picks a composer from the popular list, fetches their works, and suggests one. Useful when you don’t know what to listen to.
  • Logopus log "Brandenburg Concerto No. 3" 5 appends an entry to listening_log.json with 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.