Skip to content

Add Ampache media server backend that supports Ampache's native JSON API (API8) - #810

Open
lachlan-00 wants to merge 28 commits into
NeptuneHub:mainfrom
lachlan-00:AmpacheConnector
Open

Add Ampache media server backend that supports Ampache's native JSON API (API8)#810
lachlan-00 wants to merge 28 commits into
NeptuneHub:mainfrom
lachlan-00:AmpacheConnector

Conversation

@lachlan-00

@lachlan-00 lachlan-00 commented Jul 27, 2026

Copy link
Copy Markdown

testplan.md
report.md

AS-IS

Does not exist. AudioMuse-AI supports Jellyfin, Emby, Navidrome, Lyrion and Plex.

Ampache can be connected today via the navidrome backend, because Ampache also serves
the Subsonic API - but that path only sees what Subsonic exposes, and indexes tracks
under Subsonic's prefixed id form rather than Ampache's own ids.

TO-BE

Adds an ampache media server backend that speaks Ampache's native JSON API.

tasks/mediaserver/ampache.py implements the full provider surface: get_all_songs,
get_recent_albums, get_tracks_from_album, get_album_track_ids, search_albums,
download_track, list_libraries, test_connection, the playlist functions,
get_top_played_songs and get_lyrics.

Requires Ampache API 8 or newer (_MIN_API_MAJOR = 8). Album browsing depends on
the catalog id being present on album objects and on the cond browse filter.
test_connection warns when a server reports an older API, so the setup wizard says
so rather than an analysis quietly finding nothing.

Notes on a few choices:

  • An API key needs no handshake at all. Gatekeeper::getAuth() reads
    Authorization: Bearer before it looks at the query string, and ApiHandler
    resolves the user via findByApiKey and opens the session itself. For a key-shaped
    secret (32+ hex) the backend settles this with a single bearer ping per credential
    set, then every request carries the header and omits auth entirely - so no secret
    reaches the URL or the web server's access log. A refusal is remembered and falls
    back to the handshake; a network blip is deliberately not remembered.
  • A password never takes that path. findByApiKey would only refuse it, and being
    refused means having put the password on the wire for nothing. A password handshakes
    and sends a time-salted hash, never the password itself.
  • Sessions slide rather than expiring on a timer. Ampache's Session::extend()
    resets the expiry on every authenticated call, so the cached window is slid forward
    on each accepted request and re-issued only once it has genuinely lapsed. The real
    window comes from the server's session_expire. The 4701 retry remains the backstop.
  • MUSIC_LIBRARIES is pushed into the query, resolved to Ampache catalog ids and
    sent as cond=catalog,<id> - one browse per catalogue, since cond conditions
    combine rather than alternate. It is still enforced locally, because Ampache
    ignores a cond it does not understand instead of refusing it; a browse whose
    rows report another catalogue abandons the per-catalogue plan for one unfiltered walk
    filtered locally. A failed page is retried once with the filter intact, and a
    catalogue that cannot be completed is logged as INCOMPLETE rather than returned as
    if whole - cleaning prunes against that list.
  • AMPACHE_PAGE_SIZE (default 500) sets rows per browse page. The cost is
    server-side per-song work, not network, so raising it cuts request count and not the
    work; values above roughly 2300 cannot complete inside the 60s request timeout.
  • The dispatch loop asks for ids, not track metadata. Deciding what to enqueue only
    needs ids and a count, so it uses browse (Catalog::get_name_array) instead of
    album_songs, which skips hydrating every song's rating, art, album and artist. Any
    answer that cannot be trusted - unknown catalogue id, a total_count that disagrees
    with the rows returned - falls back to album_songs, because a short list would make
    an unfinished album look complete. This is an optional dispatcher capability
    (get_album_track_ids); the other five providers are untouched and fall back
    automatically.
  • Lyrics come from the album fetch that already returned them. Ampache serialises a
    single song and an album_songs row through the same Json8_Data::songs_array, so
    the per-track song call was re-paying that row's whole hydration cost to re-read one
    field.
  • Downloads use download, not stream, so analysis sees the original file rather than
    a transcode, and a JSON error body arriving under HTTP 200 is refused rather than
    saved as audio.
  • get_last_played_time returns None - Ampache exposes no per-track last-played
    timestamp, so recency-weighted callers fall back to play counts.
  • Track ids are Ampache's own row ids, so a library analysed through this backend is
    keyed differently from the same library through navidrome. Both dedupe to the same
    fingerprint, so a library can be registered twice with no re-analysis.

Registration is the usual set: config.MEDIASERVER_FIELDS_BY_TYPE /
MEDIASERVER_CRED_KEY_BY_FIELD / AMPACHE_* globals, _PROVIDER_NAMES,
_SUPPORTED_TYPES, _SUPPORTED_PROVIDERS in the migration probe, _SUPPORTED_TARGETS
in provider migration, and AMPACHE_PASSWORD in SECRET_FIELDS so the wizard masks it.
The setup wizard picks the fields up automatically from MEDIASERVER_FIELDS_BY_TYPE.

Test

Unit: pytest test/unit/ -q -> 3067 passed, 1 skipped. 84 cases in
test/unit/test_mediaserver_ampache.py, plus
test/unit/test_provider_registration_contract.py, which enforces that every backend
binds to every dispatcher call site and that Ampache is registered in config, the
dispatcher, both JavaScript files, both HTML dropdowns and docs/PARAMETERS.md.

Initial functional pass, against Ampache 8.0.0 (develop) in Docker with
AudioMuse-AI from deployment/docker-compose.yaml:

  1. POST /api/servers/test with server_type: ampache ->
    {"ok": true, "sample_count": 2, "path_format": "absolute"}
  2. Registered it as default via POST /api/servers, then ran POST /api/analysis/start
  3. Analysis completed; tracks downloaded and analysed, rows written to score and
    track_server_map
  4. GET /api/similar_tracks?item_id=1 returned a neighbour with a distance score
  5. Registered the same library a second time through the navidrome backend. Both appear
    in track_server_map against the same item_id fingerprints - 1/3001 (ampache,
    match_tier=path) and so-1/so-3001 (navidrome, match_tier=fingerprint) -
    confirming dedup treats them as one track and no re-analysis happens.

Verified end to end through Ampache's OpenSubsonic getSonicSimilarTracks, which
returned the expected neighbour at the expected similarity with either backend as
default.

Live-server pass, on a production Ampache 8 install with two servers registered
(the default connected as navidrome, the second through this connector). Evidence taken
from the Apache access log for the Ampache vhost, filtered to json.server.php so the
vhost's own web-UI traffic is excluded:

Check Result Evidence
Album discovery PASS 2 action=albums, paged rather than probed per album
Library filter applied server-side PASS cond=catalog,<id> present on the albums browse
API key uses the bearer header PASS 258 ping, 0 handshake, no auth= in any query string
No secret in any log PASS literal secret: 0 hits in worker/flask logs, 0 in Apache logs, 0 in the Ampache log
Lyrics reuse the album fetch PASS 0 action=song across the run with LYRICS_ENABLED=true
Dispatch loop browses for ids PASS 234 browse(type=album) vs 246 album_songs - the remaining album_songs are the album jobs fetching real metadata, one per album analysed
No silent incompleteness PASS no AMPACHE ... FETCH FAILED / INCOMPLETE / RETURNED NO ALBUMS in worker logs
Ampache-side errors PASS no server errors in the Ampache log for the window
Throughput PASS 3236 tracks downloaded and analysed in one run, no aborted pages

Not claimed, as each needs deliberate fault injection or a different configuration:
session invalidated mid-analysis, an injected page failure, a short total_count, an
ignored cond, a refused API key, and a password-configured run.

Other useful information

Ampache's side of this is a plugin implementing a new sonic-analysis plugin type, which
backs the OpenSubsonic sonicSimilarity extension (getSonicSimilarTracks,
findSonicPath). It tries both id forms, so it works with this backend or with
navidrome and needs no configuration either way.

One upstream detail worth flagging: /api/find_path returns no per-hop distance, only a
single total_distance. Consumers that assume a per-hop score will read a missing key
as 0 - i.e. a perfect match.

Checklist

Type of change:

  • New feature

Tested on architecture:

  • Intel

Tested on media server:

  • Ampache 8.0.0 (develop)
  • Navidrome (regression check - same library through both backends)
  • Jellyfin
  • Emby
  • Lyrion

Updated:

  • Documentation (docs/AMPACHE.md, docs/PARAMETERS.md)
  • Unit test
  • Integration test

Other:

  • CONTRIBUTING.md read and accepted
  • Checked performance on a big library (> 150k songs) works without issues

NOTES

get_last_played_time is coming to Ampache 8.

Page-size behaviour was measured on a 659,593-song Ampache 8 install: roughly 38
songs/second and 1.7 MB per 500-row response, with the rate not degrading as offset
grows, so cost is linear in library size. A full-library analysis run on that install is
still to come, along with demo connections via https://develop.ampache.dev
(https://demo.ampache.dev after the Ampache 8 release).

This will be released as part of Ampache 8 / API 8.

Copilot AI review requested due to automatic review settings July 27, 2026 06:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@NeptuneHub NeptuneHub left a comment

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.

Hi,
this PR missed a big chunck of functionality, I added but I don't have Ampache so please do a VERY DETAILED check of all the functionality (online and offline) with a focus on:

  • Setup Wizard: Try to install from scratch AudioMuse-AI and select Ampache as proivder;
  • Multiple provider: Still in the setup wizard, try to add a second music provider.
  • Provider Migration: try to migrate to ampache to another and vice versa;

Also please explain why the default Navidrome API, that implement Open Subosnic API, doesn't work for Ampache.

Here a details of the fix that I made that I also ask you to doublecheck:

  • create_or_replace_playlist crashed on every call because Ampache accepted 2 parameters while the dispatcher passed 3. This caused a TypeError during Sonic Fingerprint cron and Alchemy Radio runs. Fix: added user_creds=None and passed it to get_playlist_by_name.

  • Instant playlists had the wrong name because Ampache did not append _instant like the other backends. This made instant playlists collide with manually created playlists. Fix: append _instant when creating the playlist.

  • Failed downloads were written to disk as audio because the HTTP status was not checked before saving the stream. An error response could therefore be saved as an MP3 file. Fix: added raise_for_status() before returning the stream.

  • Sonic Fingerprint used ratings instead of play counts because get_top_played_songs used the highest filter. Ampache defines highest as highest rated and frequent as most played. Fix: changed highest to frequent.

  • Ampache was missing from both server type lists in the setup wizard. Fix: added the Ampache option and credential hint.

  • Ampache credential fields were not displayed in the setup wizard because static/setup.js had no Ampache configuration. The password was also not marked as secret, and values were lost when changing server type. Fix: added the URL, username, and password fields and preserved their values.

  • Ampache could not be added as a second server because static/music_servers_admin.js had no Ampache credential definition. Fix: added URL, username, and password fields.

  • Ampache could not be selected as a Provider Migration target because the option and credential fields were missing from the page. Fix: added both and updated the page introduction.

  • Migration from Ampache failed because _current_provider_creds had no Ampache branch. Fix: replaced the hardcoded provider logic with the mappings already defined in config.py. This also supports future providers automatically.

  • BASIC_SERVER_FIELDS did not include the Ampache configuration keys. Fix: added the three AMPACHE_* keys.

  • Ampache parameters were missing from docs/PARAMETERS.md, the README, and MULTI_SERVER.md. Fix: added the new parameters and updated the supported provider lists. The tested version badge was left unchanged because no Ampache version was verified.

  • The new ampache.py file broke CI because it used the wrong license header and had no Main Features: section. Fix: added the project AGPL header, the required section, and two missing # noqa: TRY400 comments.

  • The 422-line Ampache backend had no direct test coverage. Fix: added test_mediaserver_ampache.py with 18 tests.

  • There was no test to detect incomplete provider registration across Python, JavaScript, HTML, and documentation. Fix: added test_provider_registration_contract.py with 50 tests.

  • Library picker works. list_libraries returns lower case id/name like the
    other backends. Before, the wizard showed no catalogs and the admin page saved
    [object Object], so the server returned zero songs.
  • API key only installs can be saved. AMPACHE_USER is now optional, as the docs
    always said. Before, every save path rejected an empty username.
  • Playlist creation returns a dictionary, not a string. Fixes the crash in the
    Sonic Fingerprint cron and the false 500 error in the chat playlist endpoint.
  • Broken downloads are detected. Ampache sends API errors with HTTP 200 and JSON,
    which was written to disk as an audio file. An expired session now retries.
  • Recent albums respect the library filter. The backend pages until it has enough.
    Before, an install could stall and never analyse anything.
  • Full library fetch filters on the server instead of downloading everything and
    dropping most of it. Tracks keep only the 11 fields consumers read.
  • The password is no longer sent in clear text as a fallback API key in the URL.
  • A changed password no longer passes the connection test. The token cache now
    includes the password, so a rotated one cannot reuse the old session.
  • Playlist failures are visible. An empty playlist is not reported as created, and
    a failed delete stops the replace instead of creating a duplicate name.
  • Playlist lookup uses the caller credentials, not the default server.
  • The lyrics timeout also limits the login handshake, so a slow server cannot block
    the worker for 60 seconds.
  • Connection test warns on relative paths, and dead or duplicated code was removed.

@lachlan-00

Copy link
Copy Markdown
Author

I can do all that testing no problem!

I have integrated develop.ampache.dev which is a small library but for my large home library the scanning very slow process

Is there a way to add more workers or speed that up? It's still got a long way to go.

@NeptuneHub

Copy link
Copy Markdown
Owner

Please keep in mind that I just did a code review with CLAUDE, my expectations is that you finalize the review and you do real test because I don’t have Ampache installed at all. Also please give a check to SonarCloud issue.

About analysis speed up there is two important way:

  • raise a new worker, you can deploy on another machine just be sure it reach Redis, PostgreSQL and music server. In docs/architecture.md it explained a bit and in /deployment/test you have an example of docker compose of worker only;
  • be sure to have configured lyrics API OR that the music server return lyrics. This because ASR is the slowest thing, doing on few tracks as fallback is ok, if you had to do on thousand song it can take very slow.

@lachlan-00

Copy link
Copy Markdown
Author

I've set up my tester a bit better with better worker split and postgres config that should speed it up.

I will run through it all again and sonarcube as well and keep updating the pr, haven't reviewed what's changed yet just getting it set up for now so will take me a bit to complete all that

lachlan-00 and others added 17 commits July 30, 2026 08:44
* add docs/AMPACHE.md
* default to https in messaging and hints
* use _log_error function in to redact URL from logs
* catalog id's are not in responses (yet) but the albums call can filter on conditions (e.g. cond=catalog,32) so we can filter on api6 and 8 with that
Every provider reports the path it holds a track at, so an install whose
library is mounted into the container can read the file directly: no HTTP
round trip, no second copy of the bytes, and no load on the media server.
Provider-agnostic - it lives in the dispatcher, and all six backends
populate Path/FilePath.

Off by default. LOCAL_FILE_ACCESS enables it, LOCAL_FILE_ROOTS allowlists
the directories a track may come from, and LOCAL_FILE_PATH_MAP rewrites the
server's prefix onto this container's mount point.

Two properties the implementation is built around:

* The pipeline DELETES whatever download_track returns (the finally in
  tasks/analysis/album.py), so the library file is never returned. What the
  caller gets is a link inside TEMP_DIR - a symlink, or a hardlink where
  symlinks are not permitted - and removing it never touches the target.
* The path comes from the media server and is therefore untrusted. The
  REAL location is resolved first, so a symlink planted in the library
  cannot escape, and anything outside LOCAL_FILE_ROOTS is refused. With no
  root configured nothing is read at all.

Every failure returns None and the track is downloaded as before, so local
access is an optimisation that cannot become a new way for analysis to fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lachlan-00

Copy link
Copy Markdown
Author

Even with all the mitigations for speed I've put in it's going to take more than 6 months to scan my home server.

I'll do the testing with a smaller library by skipping my biggest catalog.

I've added 3 scripts that i'll squash down that i'm using as evidence to build on. and will report when done

@lachlan-00

Copy link
Copy Markdown
Author

@NeptuneHub I've updated the comment with reports and details; generated using the connector and build scripts.

I tried making the system faster to get through my library and some things have improved but i can't physically ingest my home server fast enough with all catalogs.

All features working that i can test and made a few things faster where possible and a few api updates to make it easier.

@sonarqubecloud

sonarqubecloud Bot commented Aug 1, 2026

Copy link
Copy Markdown

@NeptuneHub

NeptuneHub commented Aug 1, 2026

Copy link
Copy Markdown
Owner

While I keep revieweing this additional change, can you explain:

  • if Ampache still support Open Subosnic API;
  • Which is the pro of having this additional set of API.

I'm asking because in AudioMuse-AI analysis the speed highly depends from your HW, a good CPU can analyze in 20 second something that another require 60. If you use 2-3 computer in parallel (your homelab, maybe your desktop and your laptop) you parallelize it and you do the work in 1/3 of the time. It's not 1 second more, 1 second less, in doing the API call that do huge difference.

Meanwhile I did another round of review, please give a check. Especially avoid to change centralized logic for just adding a new Music Server or we have to retest all music server.

Regressions in centralized logic (affects the other 5 providers)

  • tasks/mediaserver/__init__.py:146 - the analysis loop lost id sanitization. The old code
    built ids with provider_item_id = sanitize_string_for_db(str(...)). The new
    get_album_track_ids fallback does bare str(track.get('Id') or track.get('id')), so
    Jellyfin, Emby, Navidrome, Lyrion and Plex all take an unsanitized fallback. A provider id
    carrying a C0 control byte can miss its sanitized work_map key, making the parent dispatch a
    redundant album job on later runs and skewing its counters. The child sanitizes/re-resolves ids
    and can still skip already-complete stages, so this is repeated fetch/planning/job churn - not
    proof that the audio is fully re-analyzed forever. It reopens the invariant covered by
    a5d391ac / f8451cbe and test/unit/test_nul_sanitization_maps.py:251.

  • static/music_servers_admin.js:163 - low-risk shared churn, not an observed regression.
    The library picker was rewritten even though Ampache returns the same supported {id, name}
    shape as the other providers. The new code is behavior-equivalent for the supported object and
    string forms, but it changes shared UI code without being required by this provider.

The other centralized changes were checked and are low risk: the map-driven
_current_provider_creds preserves the existing five providers' credential mappings,
MEDIASERVER_OPTIONAL_FIELDS_BY_TYPE has an entry only for Ampache, and the dispatcher/probe/API
gates are additive.

Bugs

  • tasks/mediaserver/ampache.py:1138 - CRITICAL: playlist creation is incompatible with API8
    in two independent ways.
    API8 returns playlist_create as
    {"playlist": [PlaylistObject]}, but :1142-1143 treats playlist as a dict and calls
    .get('id'), so a real non-empty success raises AttributeError after the server has already
    created the playlist. Even past that, :1151-1154 calls playlist_add_song with song/check;
    API8 removed that method and requires playlist_add with filter/id. Instant, clustered and
    Sonic Fingerprint playlist output is therefore unusable and can leave empty server playlists.
    See the official playlist_create / playlist_add contract.

  • tasks/mediaserver/ampache.py:1060 - analysis downloads omit format=raw and stats=0.
    API8 only guarantees original bytes with format=raw and records statistics by default.
    AudioMuse can therefore analyze transcoded bytes saved under the source extension, while every
    analysis download can be recorded as a play and pollute the data later consumed by
    get_top_played_songs. See Ampache's download contract.

  • tasks/mediaserver/ampache.py:476 - streamed HTTP errors bypass session renewal and leak the
    response.
    _stream_payload calls raise_for_status() before inspecting the Ampache error
    body. API8 maps expired handshake error 4701 to HTTP 401, so _body_error never returns
    retry and the re-handshake loop at :565-575 is unreachable for a real streamed 401. That
    branch also returns without consuming or closing the response.

  • static/setup.js:304 - the default-server Setup editor cannot save API-key-only Ampache.
    The backend correctly makes AMPACHE_USER optional, but the renderer hardcodes
    required: true for every field. With a blank username, native form validation blocks the
    whole Setup form, testConfigFieldsFilled (:642) permanently disables Test Connection, and
    providerCredsHaveSavedValues (:681) prevents automatic catalog loading for saved
    credentials. Multi-server Add/Edit and Provider Migration do allow a blank username; this bug
    is specifically the default-server editor.

  • tasks/mediaserver/ampache.py:1076 returns compatibility warnings that Setup discards.
    test_connection warns about pre-API8 servers and unusable relative/no paths, but
    app_setup.py:301-318 returns only provider type and sample count, and
    static/music_servers_admin.js:409-423 likewise displays only "Connection OK". Provider
    Migration does render the warnings. An unsupported Ampache can therefore be saved as healthy
    from both Setup server editors.

  • tasks/mediaserver/ampache.py:773 - a failed song page is returned as a successful partial
    catalog.
    _collect_songs_for reports 'failed' after retrying a page, but get_all_songs
    breaks and returns everything accumulated before the failure with no completeness signal.
    A full multi-server sweep can prune the unseen tail whenever the partial result remains above
    SWEEP_PRUNE_MIN_FETCH_RATIO (default 50%), and Provider Migration only rejects exactly zero
    target tracks (app_provider_migration.py:967-988). A non-empty partial catalog can therefore
    produce false orphans that execution deletes after the user confirms the plan.

  • tasks/mediaserver/ampache.py:855 and :951 conflate album API failure with normal
    completion.
    _album_page logs that discovery is incomplete but returns None, after which
    _collect_albums_for returns the partial list without propagating failure. Separately,
    get_tracks_from_album uses _request and returns [] for any API failure; the album worker
    treats "no tracks" as successful completion. A transient Ampache failure can silently skip an
    album until a later full run.

  • tasks/mediaserver/ampache.py:895 - finite recent-album selection is biased to the first
    selected catalog.
    Each catalog is browsed in id order and the outer loop stops as soon as the
    requested count is filled. There is no global merge/re-sort by addition_time, so with
    NUM_RECENT_ALBUMS > 0 a newer album in a later catalog can be omitted entirely.

  • tasks/mediaserver/ampache.py:592 - catalog-list failure is indistinguishable from "the
    filter matched nothing".
    _target_catalog_ids calls _request, which discards the error.
    A network failure or failed auth renewal produces an empty set, and filtered song/album callers
    return [] as if the configured catalogs did not exist.

  • tasks/mediaserver/ampache.py:607 vs :594 - a nameless catalog is unusable.
    list_libraries synthesizes "Catalog <id>" and the UI stores that label in
    MUSIC_LIBRARIES, but _target_catalog_ids matches only the real name or raw id. Selecting the
    synthesized label later resolves to zero catalogs.

  • tasks/mediaserver/ampache.py:194 and :175 - blank-user handling leaks or mixes
    credentials.
    A blank username plus a non-key secret is an invalid password-auth combination,
    but _handshake_attempts still puts that raw secret in the query string and access logs.
    Separately, user_creds.get('user') or config.AMPACHE_USER means an intentionally blank
    username for Provider Migration or a secondary API-key server inherits the default Ampache
    username. A normal hex bearer key usually still works, but cache identity and fallback
    handshake use stale account data.

  • tasks/mediaserver/ampache.py:1113 and :1130 - playlist reads are not paginated.
    Both make one limit=0 request and ignore total_count/offset. Ampache can cap the response,
    so get_playlist_by_name can miss an existing playlist and create a duplicate, while
    get_playlist_track_ids can return an incomplete track list.

  • tasks/mediaserver/ampache.py:1213 - uncached lyrics use the wrong API8 response shape, and
    the cache is not server-scoped.
    The song action returns one SongObject at the JSON root,
    but get_lyrics expects {"song": [...]}, so standalone/evicted lyrics return None. The
    album lyrics cache is also keyed only by numeric track id, so two Ampache servers with
    colliding ids can return one another's cached lyrics. See the official
    song response.

  • tasks/mediaserver/ampache.py:1035 - Provider Migration album search violates the shared
    result contract.
    It returns raw API8 AlbumObjects, where artist is an object and the count is
    songcount. templates/provider_migration.html:1349-1355 expects a scalar artist and
    track_count, so manual-match candidates render an object-like artist and omit track count.
    Sibling backends normalize {id, name, artist, year, track_count}.

  • tasks/mediaserver/ampache.py:104 - API error classification marks NOT_FOUND as an auth
    failure.
    _AUTH_ERROR_CODES contains 4704, but API8 defines that as HTTP 404 / NOT_FOUND.
    Setup can therefore show a misleading credentials error for a missing object. The official
    error mapping distinguishes 401, 403 and 404.

Docs contradicting the code (all new in this PR)

  • docs/AMPACHE.md:18-21 says native playlist creation works like the Ampache UI, but the
    implementation cannot parse playlist_create or use the API8 add action.

  • docs/AMPACHE.md:62-63 says using download means analysis always receives the original
    file.
    The code never sends the required format=raw and also omits stats=0.

  • docs/AMPACHE.md:96 documents get_last_played_time as a working Ampache 8 feature.
    tasks/mediaserver/ampache.py:1200 returns None unconditionally even though the API8
    SongObject includes nullable last_played. Sonic Fingerprint consequently gives Ampache seeds
    the unknown-recency fallback rather than the documented weighting.

  • docs/PARAMETERS.md:44 and config.py:94-97 promise that an API key never reaches the query
    string or access log.
    If bearer bootstrap is refused, the implementation deliberately falls
    back to auth=<api key> in the handshake query, so that promise is conditional rather than
    universal.

  • docs/AMPACHE.md:32 and :93-106 overstate Ampache 7 support. The guide says the connector
    works against Ampache 7 and lists only last-played/sonic_match as API8 limitations, while the
    backend's own warning says catalog ids and cond filtering require API8. An unfiltered older
    server may still work, but a filtered install does not degrade cleanly when those fields are
    absent.

  • docs/AMPACHE.md:48-51 omits the API-key-only username rule. It tells the user to enter an
    Ampache username but never explains that User may be left blank in key-only mode, despite the
    configuration and server-side validation explicitly supporting that mode.

Forgot to update

  • app_setup.py:142 - MEDIASERVER_OPTIONAL_FIELDS_BY_TYPE is missing from
    HIDDEN_ADVANCED_FIELDS.
    The internal map appears as editable JSON under Advanced/Other.
    Runtime validation is not altered because config.py excludes it from DB overrides, but the UI
    is confusing and can store a useless stale row.

  • config.py:34 still omits Ampache from the MEDIASERVER_TYPE values comment.

  • mkdocs.yml:8 and README.md:47 do not link the new Ampache guide.
    docs/AMPACHE.md is absent from the documentation nav/index and is therefore poorly
    discoverable. The README support badge (:3) and provider introduction (:22) also omit
    Ampache.

  • docs/FAQ.md:31 and docs/PARAMETERS.md:70 omit Ampache's library-filter terminology.
    Ampache accepts catalog names or raw catalog ids; the current text discusses only Lyrion paths
    and the other providers' library/folder names.

  • templates/setup.html:184, app_provider_migration.py:211, CONTRIBUTING.md:36,
    docs/ARCHITECTURE.md, docs/DEPLOYMENT.md and docs/ALGORITHM.md retain old provider lists.

    The migration OpenAPI summary also omits Plex.

  • tasks/mediaserver/ampache.py:103 duplicates AMPACHE_PAGE_SIZE's 500 default.
    This is only the fallback when the configured value is non-positive, so it is a maintenance
    drift risk rather than a runtime or centralized-defaults regression.

  • test/unit/test_mediaserver_ampache.py:124-164 encodes the invalid playlist contract.
    It mocks {"playlist": {"id": 42}} and successful playlist_add_song calls instead of the
    API8 list response and playlist_add action. The download tests (:302-326) never assert
    format=raw, stats=0; the session-expiry test (:279-300) models 4701 as HTTP 200; and lyrics
    tests (:1069-1098) mock the wrong {"song": [...]} envelope.

  • test/unit/test_analysis.py:805 and the provider contract tests cover only clean ids.
    They copy/assert the raw fallback but never send a C0-bearing id through
    get_album_track_ids into the parent work_map lookup. There is likewise no browser-level
    assertion that optional AMPACHE_USER remains non-required, or that the new optional-fields
    map stays hidden from Advanced Setup.

  • The 6x6 Provider Migration matrix uses fakes. It covers Ampache as source and target but
    does not prove a live Ampache API contract or that target credential JSON is persisted exactly
    as intended.

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.

3 participants