Skip to content

feat(library): add media library browsing API - #382

Open
sb-3000 wants to merge 2 commits into
hyperblast:masterfrom
sb-3000:library-api
Open

feat(library): add media library browsing API#382
sb-3000 wants to merge 2 commits into
hyperblast:masterfrom
sb-3000:library-api

Conversation

@sb-3000

@sb-3000 sb-3000 commented Aug 15, 2026

Copy link
Copy Markdown

Media library API (foobar2000)

Adds read access to the foobar2000 media library, plus the two operations a
library browser needs: artwork and adding to playlists.

Endpoints

GET /api/library/info{supported, enabled, itemCount}

GET /api/library/items/{offset}:{count}columns (required), view,
path, query, sort, desc

  • view=flat (default): paged track list, optional query filter and
    title-format sort
  • view=folders: one tree level — subfolders first with a recursive
    itemCount, then tracks with the requested columns. Response carries
    path, parentPath (absent at top level) and pathSeparator

GET /api/artwork/library?path=&subsong= — artwork without going through a
playlist. A folder prefers its own image file (folder/cover/front/
album/artwork × common extensions), falling back to the first track's art.

POST /api/library/items/add{plref, path, subsong, query, index, replace, play}. Resolves the selection to metadb handles server side and inserts them
with playlist_insert_items. Requires changePlaylists.

Notes on design

  • Node paths are relative to media library folders
    (library_manager::get_relative_path), so browsing starts at library folders
    rather than filesystem roots. Multiple library folders merge into one tree.
  • A track is identified by (path, subsong). Cue sheets and SACD images put
    several tracks in one file, so subsong is exposed as a first-class field
    on track nodes.
  • Because add inserts handles directly, a single subsong can be added on its
    own, and the music-directories restriction does not apply — nothing is
    resolved from client-supplied filesystem paths.
  • Other players are unaffected: base Player reports supported: false and
    the endpoints return 501.

Known limitations

  • No caching: every call enumerates the library and computes a relative path
    per item. Fine for interactive use; a folder-thumbnail grid multiplies it.
  • Folder artwork lookup does filesystem probing on the player work queue.
  • No library event key for /api/query, so clients cannot observe library
    changes.
  • API only — no web UI changes.
  • Folder ordering is a byte-wise string compare (case-sensitive).

Testing

API tests in js/api_tests/src/library_api_tests.js plus a permissions case.
Verified manually against a 9540-track library including cue-sheet albums and
SACD ISOs; the automated tests run against an empty library, so tree
resolution and subsong handling are not covered by CI.

@hyperblast

hyperblast commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Hello, Sergey.

This is really cool. 🎉

There are few minor corrections I'd like to have, but overall looks good to me.

Folder artwork lookup does filesystem probing on the player work queue.

I'm going to rework object composition a little bit to provide Player implementations with ThreadPoolWorkQueue, so this could be avoided, but I think for the first iteration we can keep things as-is.

Because add inserts handles directly, a single subsong can be added on its
own, and the music-directories restriction does not apply — nothing is
resolved from client-supplied filesystem paths.

This is OK to me. User configured media library in this way.

the automated tests run against an empty library, so tree
resolution and subsong handling are not covered by CI.

I'll take care of this.

By the way, did you consider adding API for browsing nodes with grouping other than FS entries, e.g. Artist/Album/Track, etc ?

Comment thread cpp/server/foobar2000/player_library.cpp Outdated
Comment thread cpp/server/foobar2000/player_library.cpp Outdated
Comment thread cpp/server/foobar2000/player_library.cpp Outdated
Comment thread cpp/server/library_controller.cpp Outdated
Comment thread cpp/server/library_controller.cpp Outdated
Comment thread cpp/server/foobar2000/player_library.cpp
Comment thread cpp/server/foobar2000/player_library.cpp Outdated
Comment thread cpp/server/foobar2000/player_library.cpp Outdated
@sb-3000

sb-3000 commented Aug 16, 2026

Copy link
Copy Markdown
Author

Hello, Sergey.

This is really cool. 🎉

There are few minor corrections I'd like to have, but overall looks good to me.

Folder artwork lookup does filesystem probing on the player work queue.

I'm going to rework object composition a little bit to provide Player implementations with ThreadPoolWorkQueue, so this could be avoided, but I think for the first iteration we can keep things as-is.

Because add inserts handles directly, a single subsong can be added on its
own, and the music-directories restriction does not apply — nothing is
resolved from client-supplied filesystem paths.

This is OK to me. User configured media library in this way.

the automated tests run against an empty library, so tree
resolution and subsong handling are not covered by CI.

I'll take care of this.

By the way, did you consider adding API for browsing nodes with grouping other than FS entries, e.g. Artist/Album/Track, etc ?

Thanks, yep, grouping by Artist/Album/Genre on server side will make library api complete. I'll prepare another PR soon


routes.get("info", &LibraryController::getInfo);
routes.get("items/:range", &LibraryController::getItems);
routes.get("browse/:range", &LibraryController::browse);

@hyperblast hyperblast Aug 16, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could I suggest you URLs format?

/api/library/items              <- flat result
/api/library/items/by-path      <- grouped by directory structure
/api/library/items/by-columns   <- grouped by title formatting expressions

routes.get("info", &LibraryController::getInfo);
routes.get("items/:range", &LibraryController::getItems);
routes.get("browse/:range", &LibraryController::browse);
routes.post("items/add", ControllerAction<LibraryController>(&LibraryController::addItems));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like /api/library/items/add-to-playlist/:plref.

Otherwise it might look like this method adds something to library.

@hyperblast hyperblast Aug 16, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe even moving this under playlists prefix would look more natural, e.g.

/api/playlists/:plref/items/add-from-library

return Response::json({{"libraryNodes", player_->getLibraryNodes(query, range, columnsQuery.get())}});
}

ResponsePtr LibraryController::addItems()

@hyperblast hyperblast Aug 16, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint is for adding query result.

What if consumer (e.g. web UI) already have set of item paths (plus subsong) and wants to add them as-is?

@hyperblast hyperblast Aug 16, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think we need to add something like itemPath/subsong in query results, so the consumer can refer to found library items in such API.

For grouped query result such information exists, but for flat query result there is no such info.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants