mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
003b280a77
* feat(enrichment): oximedia BPM/mood facts, mood search, and queue UI Run client-side oximedia analysis after CPU seed and persist BPM, mood JSON, and searchable mood_tag facts. Add product mood groups (joy/sadness/dance/work/ romance) with Advanced Search filter on the local index, queue BPM/mood display, migration 008 mood_tag index, and refreshed licenses for oximedia crates. * fix(enrichment): keep mood_groups module comment in English * feat(search): virtual mood groups, anger filter, and Advanced Search UX Expand mood search via overlapping virtual groups (tag expansion only), add anger/Злость group, skip album/artist shortcuts for track-only filters, and simplify mood search UI (songs-only, hide type tabs). Fix CustomSelect spurious scrollbar on short option lists. * feat(analysis): unified track analysis plan and enqueue path Add TrackAnalysisPlan (waveform, LUFS, enrichment) with a single enqueue_track_analysis entry for all byte-backed triggers. Run enrichment when cache is full but library facts are missing; route playback, cache, and backfill through the planner. Fix browseTextSearch LocalSearchOpts tsc gap and remove obsolete read_seed_bytes_if_needed helper. * fix(analysis): wire playback dispatch, preload enrichment, and UI refresh Route stream, gapless, preload, and local-file playback through analysis_dispatch so BPM/mood enrichment runs when waveform/LUFS are already cached. Fix audio_preload cache-hit and hot-cache paths, emit preload-cancelled for retry, and add analysis:enrichment-updated plus content_cache_coverage key resolution. * fix(audio): preload local files from disk and stop analysis retry loop Seed hot/offline next tracks via LocalFilePlayback (512 MiB) instead of copying into the RAM preload slot. Keep bytePreloadingId set after preload-ready so progress ticks do not re-invoke audio_preload every second. * fix(enrichment): clippy, album bpm filter routing, and queue mood display Clippy-clean analysis_dispatch and engine imports; restrict track-derived album routing to mood_group/mood_tag only so bpm is skipped on album queries. Log enrichment plan errors with retry-all plan; filter queue mood labels to oximedia ids. * fix(enrichment): simplify mood_tag backfill branch in plan_track_enrichment Remove empty if-block; keep same behaviour when backfill fails and moods row exists. * docs: CHANGELOG and credits for track enrichment PR #863 * docs(credits): track enrichment PR #863 contributor line * chore(enrichment): clippy-clean plan branch and trim dead exports Collapse mood_tag backfill if for clippy; remove unused moodGroupById and OXIMEDIA_MOOD_LABELS re-exports; stop poll when server BPM is already known. * fix(enrichment): close R2/S1–S3 limits and Song Info BPM fallback Return TrackEnrichmentOutcome::Failed on oximedia errors so retries are not masked as complete; extract mood Advanced Search SQL, unify top-3 mood tag selection in mood_groups with TS invariant tests, and show measured BPM in Song Info when tag BPM is missing or zero. * fix(enrichment): restore offline coverage and show mood in Song Info Add unit tests for offline download cancel/clear registry after the analysis seed refactor dropped read_seed_bytes coverage; show localized mood labels in Song Info when library enrichment facts exist. * fix(enrichment): soft mood scoring and unblock offline cancel tests Replace oximedia quadrant happy/excited mapping with valence/arousal soft scores across all mood tags for display, storage, and backfill; fix offline cancel unit tests that deadlocked by calling clear while holding the global offline_cancel_flags mutex. * fix(enrichment): dedupe joy cluster and cap mood display at two labels Never show happy and excited together; pick one tag per V/A cluster, tighten oximedia recalibration, and limit queue/Song Info to two moods that pass a relative score floor. * fix(enrichment): disable oximedia mood labels in UI and search tags Oximedia 0.1.7 mood is a spectral energy heuristic, not independent mood weights; valence correlates with loud/bright audio and false-labels metal and lyrical tracks as happy. Hide queue/Song Info mood and stop writing mood_tag facts until a reliable detector lands; keep V/A/moods JSON stored. * fix(enrichment): disable oximedia mood analysis and add BPM advanced search Stop planning, running, and storing oximedia mood facts; purge accumulated mood rows via migration 009. Hide mood filters in Advanced Search, expose BPM range filter with dual-storage resolution, and show a BPM column in song results when that filter is active. * feat(search): analysis BPM priority, source tooltip, and filter UX Prefer analysis track_fact over file tags for BPM resolution; show source in list tooltips. Validate BPM range on blur, add clear button, fix double tooltip. * fix(enrichment): prefer analysis BPM in Song Info and queue tech row Show measured track_fact BPM before file tags until analysis completes; pick the highest-confidence analysis fact when several exist.
264 lines
6.4 KiB
TypeScript
264 lines
6.4 KiB
TypeScript
import type { SubsonicServerIdentity } from '../utils/server/subsonicServerIdentity';
|
|
|
|
export interface SubsonicAlbum {
|
|
id: string;
|
|
name: string;
|
|
artist: string;
|
|
artistId: string;
|
|
coverArt?: string;
|
|
songCount: number;
|
|
duration: number;
|
|
playCount?: number;
|
|
year?: number;
|
|
genre?: string;
|
|
starred?: string;
|
|
recordLabel?: string;
|
|
created?: string;
|
|
/** Present on some servers (e.g. OpenSubsonic) for album-level rating. */
|
|
userRating?: number;
|
|
/** OpenSubsonic: true when the album is tagged as a compilation. */
|
|
isCompilation?: boolean;
|
|
/** OpenSubsonic: release types from MusicBrainz tags (e.g. "Album", "EP", "Single", "Compilation", "Live"). */
|
|
releaseTypes?: string[];
|
|
/** OpenSubsonic: structured album-artist credits (e.g. featured guests on the album). */
|
|
artists?: SubsonicOpenArtistRef[];
|
|
/** OpenSubsonic: single-string album-artist for display (mirrors `artists` joined). */
|
|
displayArtist?: string;
|
|
/** OpenSubsonic: per-disc subtitles (e.g. "Sessions" on CD 3 of a deluxe edition). */
|
|
discTitles?: SubsonicDiscTitle[];
|
|
}
|
|
|
|
export interface SubsonicDiscTitle {
|
|
disc: number;
|
|
title: string;
|
|
}
|
|
|
|
/** OpenSubsonic `artists` / `albumArtists` entries on a child song (may include `userRating`). */
|
|
export interface SubsonicOpenArtistRef {
|
|
id?: string;
|
|
name?: string;
|
|
userRating?: number;
|
|
/** Navidrome / alternate OpenSubsonic payloads (same meaning as `userRating`). */
|
|
rating?: number;
|
|
}
|
|
|
|
export interface SubsonicSong {
|
|
id: string;
|
|
title: string;
|
|
artist: string;
|
|
album: string;
|
|
albumId: string;
|
|
artistId?: string;
|
|
duration: number;
|
|
track?: number;
|
|
discNumber?: number;
|
|
coverArt?: string;
|
|
year?: number;
|
|
userRating?: number;
|
|
/** Some OpenSubsonic responses attach parent ratings on child songs. */
|
|
albumUserRating?: number;
|
|
artistUserRating?: number;
|
|
artists?: SubsonicOpenArtistRef[];
|
|
albumArtists?: SubsonicOpenArtistRef[];
|
|
// Audio technical info
|
|
bitRate?: number;
|
|
suffix?: string;
|
|
contentType?: string;
|
|
size?: number;
|
|
samplingRate?: number;
|
|
bitDepth?: number;
|
|
channelCount?: number;
|
|
starred?: string;
|
|
genre?: string;
|
|
path?: string;
|
|
albumArtist?: string;
|
|
/** ISRC code when available (e.g., Navidrome) */
|
|
isrc?: string;
|
|
/** Times the track has been played, surfaced by Navidrome's Subsonic API. */
|
|
playCount?: number;
|
|
/** ISO datetime of the last play, surfaced by Navidrome (OpenSubsonic). */
|
|
played?: string;
|
|
/** Beats per minute, surfaced by Navidrome when the tag is set on the file. */
|
|
bpm?: number;
|
|
/** Local index Advanced Search: `'tag'` (server/file tag) or `'analysis'` (measured). */
|
|
localBpmSource?: 'tag' | 'analysis';
|
|
replayGain?: {
|
|
trackGain?: number;
|
|
albumGain?: number;
|
|
trackPeak?: number;
|
|
albumPeak?: number;
|
|
};
|
|
/** OpenSubsonic: structured composer credit (string for back-compat). */
|
|
displayComposer?: string;
|
|
/** OpenSubsonic: structured contributors list — Navidrome ≥ 0.55. */
|
|
contributors?: Array<{
|
|
role: string;
|
|
subRole?: string;
|
|
artist: { id?: string; name: string };
|
|
}>;
|
|
}
|
|
|
|
export interface InternetRadioStation {
|
|
id: string;
|
|
name: string;
|
|
streamUrl: string;
|
|
homepageUrl?: string;
|
|
coverArt?: string; // Navidrome v0.61.0+
|
|
}
|
|
|
|
export interface RadioBrowserStation {
|
|
stationuuid: string;
|
|
name: string;
|
|
url: string;
|
|
favicon: string;
|
|
tags: string;
|
|
}
|
|
|
|
export interface SubsonicPlaylist {
|
|
id: string;
|
|
name: string;
|
|
songCount: number;
|
|
duration: number;
|
|
created: string;
|
|
changed: string;
|
|
owner?: string;
|
|
public?: boolean;
|
|
comment?: string;
|
|
coverArt?: string;
|
|
}
|
|
|
|
export interface SubsonicNowPlaying extends SubsonicSong {
|
|
username: string;
|
|
minutesAgo: number;
|
|
playerId: number;
|
|
playerName: string;
|
|
}
|
|
|
|
export interface SubsonicArtist {
|
|
id: string;
|
|
name: string;
|
|
albumCount?: number;
|
|
coverArt?: string;
|
|
starred?: string;
|
|
/** Present on some servers (e.g. OpenSubsonic) for artist-level rating. */
|
|
userRating?: number;
|
|
}
|
|
|
|
export interface SubsonicGenre {
|
|
value: string;
|
|
songCount: number;
|
|
albumCount: number;
|
|
}
|
|
|
|
export interface SubsonicMusicFolder {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface SubsonicArtistInfo {
|
|
biography?: string;
|
|
musicBrainzId?: string;
|
|
lastFmUrl?: string;
|
|
smallImageUrl?: string;
|
|
mediumImageUrl?: string;
|
|
largeImageUrl?: string;
|
|
similarArtist?: Array<{ id: string; name: string; albumCount?: number }>;
|
|
}
|
|
|
|
export interface SubsonicDirectoryEntry {
|
|
id: string;
|
|
parent?: string;
|
|
title: string;
|
|
isDir: boolean;
|
|
album?: string;
|
|
artist?: string;
|
|
albumId?: string;
|
|
artistId?: string;
|
|
coverArt?: string;
|
|
duration?: number;
|
|
track?: number;
|
|
year?: number;
|
|
bitRate?: number;
|
|
suffix?: string;
|
|
size?: number;
|
|
genre?: string;
|
|
starred?: string;
|
|
userRating?: number;
|
|
}
|
|
|
|
export interface SubsonicDirectory {
|
|
id: string;
|
|
parent?: string;
|
|
name: string;
|
|
child: SubsonicDirectoryEntry[];
|
|
}
|
|
|
|
export type PingWithCredentialsResult = SubsonicServerIdentity & { ok: boolean };
|
|
|
|
export interface RandomSongsFilters {
|
|
size?: number;
|
|
genre?: string;
|
|
fromYear?: number;
|
|
toYear?: number;
|
|
}
|
|
|
|
export interface StatisticsLibraryAggregates {
|
|
playtimeSec: number;
|
|
albumsCounted: number;
|
|
songsCounted: number;
|
|
capped: boolean;
|
|
genres: SubsonicGenre[];
|
|
}
|
|
|
|
export interface StatisticsOverviewData {
|
|
recent: SubsonicAlbum[];
|
|
frequent: SubsonicAlbum[];
|
|
highest: SubsonicAlbum[];
|
|
artistCount: number;
|
|
}
|
|
|
|
export interface StatisticsFormatSample {
|
|
rows: { format: string; count: number }[];
|
|
sampleSize: number;
|
|
}
|
|
|
|
export interface SearchResults {
|
|
artists: SubsonicArtist[];
|
|
albums: SubsonicAlbum[];
|
|
songs: SubsonicSong[];
|
|
}
|
|
|
|
export interface StarredResults {
|
|
artists: SubsonicArtist[];
|
|
albums: SubsonicAlbum[];
|
|
songs: SubsonicSong[];
|
|
}
|
|
|
|
export type EntityRatingSupportLevel = 'track_only' | 'full';
|
|
|
|
export interface AlbumInfo {
|
|
largeImageUrl?: string;
|
|
mediumImageUrl?: string;
|
|
smallImageUrl?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
export const RADIO_PAGE_SIZE = 25;
|
|
|
|
export interface SubsonicLyricLine {
|
|
start?: number; // milliseconds — absent when unsynced
|
|
value: string;
|
|
}
|
|
|
|
export interface SubsonicStructuredLyrics {
|
|
/** OpenSubsonic spec field name (Navidrome ≥ 0.50.0 / any OpenSubsonic server). */
|
|
synced?: boolean;
|
|
/** Legacy / alternate casing used by some older Subsonic-compatible servers. */
|
|
issynced?: boolean;
|
|
lang?: string;
|
|
offset?: number;
|
|
displayArtist?: string;
|
|
displayTitle?: string;
|
|
line: SubsonicLyricLine[];
|
|
}
|