From 1015135a4b2399ad12afde40ca4c4fca51b9fcaa Mon Sep 17 00:00:00 2001 From: Nils Stinnesbeck Date: Sun, 25 Oct 2026 12:27:12 +0100 Subject: [PATCH] Filter dialog: Language right after Quality Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ SPECS.md | 4 ++-- internal/ui/help.go | 4 ++-- internal/ui/libfilter.go | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e48421..5d2cb17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- **Filter dialog** (Library and release list): Language comes right after Quality, so the two + most used filters are at the top. - **Downloading episodes in a series' seasons and episodes:** like the Library, the drill-down marks episodes in Sonarr's download queue with a blue dot and "downloading 45%" as their status (details: "Downloading 45% · 0:01:40 left"), and seasons with such episodes with a blue dot. diff --git a/SPECS.md b/SPECS.md index 8e71999..4596288 100644 --- a/SPECS.md +++ b/SPECS.md @@ -57,7 +57,7 @@ Notes: - **Config:** load URLs + API keys from a config file; fail with a clear message if missing or unreachable. - **Search:** a Movies view (Radarr) and a Series view (Sonarr), each with a search box. Results list with title, year, rating, status, and an "in library" marker. Detail pane with overview, runtime, genres, network/studio. - **Add:** from a result, open a small form: quality profile, root folder, monitored (yes/no), "search now" (yes/no); for series also monitor mode (all / future / first season / none). Submit calls `POST /movie` or `POST /series` with `addOptions.searchForMovie` / `searchForMissingEpisodes`. -- **Interactive release search:** for a movie (or a series' season/episode) in the library, list releases from indexers (`GET /release`) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (`POST /release`). Rejected releases can be grabbed only after an explicit confirmation. The list can be filtered by quality, codec (x265/x264/AV1), HDR/DV/SDR, audio format and language; releases carry no media info, so codec and HDR are read from the title, quality and custom formats (a release that does not name its codec never matches a codec filter). +- **Interactive release search:** for a movie (or a series' season/episode) in the library, list releases from indexers (`GET /release`) with title, indexer, size, quality, age, and rejection reasons; sort/filter; grab a chosen one (`POST /release`). Rejected releases can be grabbed only after an explicit confirmation. The list can be filtered by quality, language, codec (x265/x264/AV1), HDR/DV/SDR and audio format; releases carry no media info, so codec and HDR are read from the title, quality and custom formats (a release that does not name its codec never matches a codec filter). - **Downloads view:** all downloads and queues in one place, auto-refreshing every few seconds: - SABnzbd queue (name, size, progress, ETA, speed) and history (completed / failed, with retry). - Radarr and Sonarr queues (status, warnings, import errors), so stuck imports are visible. @@ -124,7 +124,7 @@ Keybindings (the status bar shows the ones that apply to the current view): | `/` | Movies, Series, Library: focus the search / text filter | | `Enter` | Movies, Series: add the selected result (dialog: ↑↓ move, ←→ change dropdowns) | | `i` | Movies, Series, Library: interactive release search (series: seasons and episodes) | -| `F` | Library and release list: filter by properties (quality, codec, HDR/DV, audio, language) | +| `F` | Library and release list: filter by properties (quality, language, codec, HDR/DV, audio; in this order in the dialog) | | `f` | Library: cycle the status filter; release list: hide rejected releases | | `[` `]` `t` `u` | Calendar: week back / forward, today, include unmonitored | | `d` | Library: remove the item from the library (asks first) | diff --git a/internal/ui/help.go b/internal/ui/help.go index 16f9fe6..02cc413 100644 --- a/internal/ui/help.go +++ b/internal/ui/help.go @@ -35,7 +35,7 @@ var helpSections = []helpSection{ {title: "Library", note: "The dot before a title: green when monitored, grey hollow when not, blue while it is in the Radarr/Sonarr download queue (the details say how far).", items: []helpItem{ {"Tab", "switch between the movies and series lists"}, {"f", "cycle the status filter (all, missing, downloaded, monitored, unmonitored); every list has its own filters"}, - {"F", "filter by file properties: quality, codec (x265, x264, AV1), HDR / DV / SDR, audio (Atmos, TrueHD, DTS, DD+ ...), language. Changes apply live behind the dialog, also while you move through an open list; Esc undoes them. Options that would leave no results are not offered. Series files are read on demand."}, + {"F", "filter by file properties: quality, language, codec (x265, x264, AV1), HDR / DV / SDR, audio (Atmos, TrueHD, DTS, DD+ ...). Changes apply live behind the dialog, also while you move through an open list; Esc undoes them. Options that would leave no results are not offered. Series files are read on demand."}, {"/", "text filter for the focused list only; each list keeps its own text (Enter or Esc returns to the list)"}, {"t", "toggle monitored"}, {"d", "remove from the library (files are kept unless you tick \"Delete files\")"}, @@ -59,7 +59,7 @@ var helpSections = []helpSection{ {"Enter, g", "grab the selected release"}, {"s", "cycle the sort (best, size, age)"}, {"f", "hide rejected releases"}, - {"F", "filter by quality, codec, HDR / DV, audio, language (read from the release title, quality and custom formats)"}, + {"F", "filter by quality, language, codec, HDR / DV, audio (read from the release title, quality and custom formats)"}, }}, {title: "Downloads", items: []helpItem{ {"p", "pause / resume the SABnzbd queue"}, diff --git a/internal/ui/libfilter.go b/internal/ui/libfilter.go index e16462c..701ab9e 100644 --- a/internal/ui/libfilter.go +++ b/internal/ui/libfilter.go @@ -129,10 +129,10 @@ func staticOptions(o []string) func(filterPool) []string { var facetDefs = []facet{ {"Quality", staticOptions(resOptions), func(f attrFilter) string { return f.res }, func(f attrFilter, v string) attrFilter { f.res = v; return f }, nil, nil}, + {"Language", filterPool.languages, func(f attrFilter) string { return f.lang }, func(f attrFilter, v string) attrFilter { f.lang = v; return f }, nil, nil}, {"Codec", staticOptions(codecOptions), func(f attrFilter) string { return f.codec }, func(f attrFilter, v string) attrFilter { f.codec = v; return f }, nil, nil}, {"HDR", staticOptions(hdrOptions), func(f attrFilter) string { return f.hdr }, func(f attrFilter, v string) attrFilter { f.hdr = v; return f }, nil, nil}, {"Audio", staticOptions(audioOptions), func(f attrFilter) string { return f.audio }, func(f attrFilter, v string) attrFilter { f.audio = v; return f }, nil, nil}, - {"Language", filterPool.languages, func(f attrFilter) string { return f.lang }, func(f attrFilter, v string) attrFilter { f.lang = v; return f }, nil, nil}, } // listedValues returns the options a facet offers: those that, together with @@ -153,8 +153,8 @@ func listedValues(f facet, state attrFilter, pool filterPool) []string { return vals } -// openAttrFilter shows the dialog to pick quality, codec, HDR, audio and -// language. Every change is passed to set immediately so the list behind the +// openAttrFilter shows the dialog to pick quality, language, codec, HDR and +// audio (the most used first). Every change is passed to set immediately so the list behind the // dialog follows along, also while moving through an open list; Cancel and Esc // restore cur. Entries that would leave no results are not offered. The // returned function re-evaluates them when the pool has changed; onClose runs