Skip to content

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

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

feat(library): add media library browsing API#382
sb-3000 wants to merge 3 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

Comment thread cpp/server/library_controller.cpp Outdated

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sounds good, /api/library/items stays flat, the way the SDK returns

But grouped results has the same response schema, so how about the same url with a parameter:

/api/library/items/{range}?group_by=%folder%|%artist%|%album%

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.

You'll also need current values for %folder% and %artist%,
directory grouping request however wants current directory path.

So to summarize:

Common parameters:

  • paging parameters
  • ordering parameters
  • list of columns to return
  • user defined search string

Specific to directory structure:

  • current path

Custom grouping structure:

  • N title formatting expressions for grouping
  • N-1 values for specify current "path" within grouping structure

Combining these into single endpoint would be difficult to understand for consumers.
Underlying implementation likely will be different as well.

@hyperblast hyperblast Aug 18, 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.

Other random note:

Maybe moving range parameter from URL to query string would be more easy to read.

Playlists are naturally ordered, but with media library we first apply search criteria and then paging.

Current URL structure pretends other way.

We can have some default value for range e.g. 0:1000, which will cover typical use cases.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fair points. Default of 0:1000 isn't the same as a maximum, I would rather keep it unbounded by default

Comment thread cpp/server/library_controller.cpp Outdated
Comment thread cpp/server/library_controller.cpp Outdated
@hyperblast

Copy link
Copy Markdown
Owner

@sb-3000 I have two ideas

  • I can setup some integration branch in my repo, so changes could be accumulated in smaller PRs and then merged to master
  • I can take care of polishing endpoints myself, if you're busy with other tasks, but we need to clarify which parts you want to work on to minimize conflicts

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