151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
package ui
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/rivo/tview"
|
|
|
|
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
|
|
)
|
|
|
|
// attrFilter narrows the library by properties of the downloaded files. An
|
|
// empty field means "any". All fields must match the same file; a series
|
|
// matches when any of its files does.
|
|
type attrFilter struct {
|
|
res, codec, hdr, lang string
|
|
}
|
|
|
|
const anyOption = "any"
|
|
|
|
var (
|
|
resOptions = []string{"2160p", "1080p", "720p", "SD"}
|
|
codecOptions = []string{arr.CodecX265, arr.CodecX264, arr.CodecAV1}
|
|
hdrOptions = []string{"HDR", "DV", "SDR"}
|
|
)
|
|
|
|
func (f attrFilter) active() bool { return f != attrFilter{} }
|
|
|
|
// String lists the active criteria for the header, e.g. "1080p x265 DV".
|
|
func (f attrFilter) String() string {
|
|
var parts []string
|
|
for _, p := range []string{f.res, f.codec, f.hdr, f.lang} {
|
|
if p != "" {
|
|
parts = append(parts, p)
|
|
}
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func (f attrFilter) matchFile(a arr.FileAttrs) bool {
|
|
switch f.res {
|
|
case "":
|
|
case "SD":
|
|
if a.Resolution <= 0 || a.Resolution > 576 {
|
|
return false
|
|
}
|
|
default:
|
|
if f.res != itoa(a.Resolution)+"p" {
|
|
return false
|
|
}
|
|
}
|
|
if f.codec != "" && f.codec != a.Codec {
|
|
return false
|
|
}
|
|
switch f.hdr {
|
|
case "HDR":
|
|
if !a.HDR {
|
|
return false
|
|
}
|
|
case "DV":
|
|
if !a.DV {
|
|
return false
|
|
}
|
|
case "SDR":
|
|
if a.HDR || a.DV {
|
|
return false
|
|
}
|
|
}
|
|
if f.lang != "" && !slices.ContainsFunc(a.Languages, func(l string) bool { return strings.EqualFold(l, f.lang) }) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// match reports whether any of the files matches. Without files nothing matches.
|
|
func (f attrFilter) match(files []arr.FileAttrs) bool {
|
|
return slices.ContainsFunc(files, f.matchFile)
|
|
}
|
|
|
|
// languagesOf collects the languages of all known files, sorted.
|
|
func languagesOf(items ...[]libItem) []string {
|
|
seen := map[string]bool{}
|
|
var out []string
|
|
for _, list := range items {
|
|
for _, it := range list {
|
|
for _, a := range it.attrs {
|
|
for _, l := range a.Languages {
|
|
if !seen[l] {
|
|
seen[l] = true
|
|
out = append(out, l)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
slices.Sort(out)
|
|
return out
|
|
}
|
|
|
|
func indexOf(options []string, v string) int {
|
|
if i := slices.Index(options, v); i >= 0 {
|
|
return i + 1 // option 0 is "any"
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// openAttrFilter shows the dialog to pick quality, codec, HDR and language.
|
|
func (a *App) openAttrFilter(cur attrFilter, languages []string, apply func(attrFilter)) {
|
|
if cur.lang != "" && !slices.Contains(languages, cur.lang) {
|
|
languages = append([]string{cur.lang}, languages...)
|
|
}
|
|
withAny := func(o []string) []string { return append([]string{anyOption}, o...) }
|
|
const (
|
|
lRes = "Quality"
|
|
lCodec = "Codec"
|
|
lHDR = "HDR"
|
|
lLang = "Language"
|
|
)
|
|
form := tview.NewForm().
|
|
AddDropDown(lRes, withAny(resOptions), indexOf(resOptions, cur.res), nil).
|
|
AddDropDown(lCodec, withAny(codecOptions), indexOf(codecOptions, cur.codec), nil).
|
|
AddDropDown(lHDR, withAny(hdrOptions), indexOf(hdrOptions, cur.hdr), nil).
|
|
AddDropDown(lLang, withAny(languages), indexOf(languages, cur.lang), nil)
|
|
|
|
pick := func(label string, options []string) string {
|
|
i := selected(form, label)
|
|
if i <= 0 {
|
|
return ""
|
|
}
|
|
return options[i-1]
|
|
}
|
|
form.AddButton("Apply", func() {
|
|
a.closeModal()
|
|
apply(attrFilter{
|
|
res: pick(lRes, resOptions),
|
|
codec: pick(lCodec, codecOptions),
|
|
hdr: pick(lHDR, hdrOptions),
|
|
lang: pick(lLang, languages),
|
|
})
|
|
})
|
|
form.AddButton("Reset", func() {
|
|
a.closeModal()
|
|
apply(attrFilter{})
|
|
})
|
|
form.AddButton("Cancel", a.closeModal)
|
|
form.SetCancelFunc(a.closeModal)
|
|
arrowNavigation(form)
|
|
form.SetBorder(true).SetTitle(" Filter by file properties ")
|
|
a.openModal(form, 50, 13)
|
|
}
|