Skip to content

Commit

Permalink
Merge branch 'main' into issue-76-clarify-search-results
Browse files Browse the repository at this point in the history
Signed-off-by: Sean Russell <60757196+xxxserxxx@users.noreply.github.com>
  • Loading branch information
xxxserxxx authored Oct 23, 2024
2 parents 6c02269 + 18c3f9b commit 0346ac9
Show file tree
Hide file tree
Showing 15 changed files with 312 additions and 27 deletions.
40 changes: 38 additions & 2 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
- "*.md"
workflow_dispatch:

env:
TERMSHOT_VERSION: "0.2.10"

jobs:
build:
strategy:
Expand All @@ -17,7 +20,7 @@ jobs:
- ubuntu-latest
go:
- "1.22"
- "1.23"
- "stable"
architecture:
- amd64
- arm64
Expand Down Expand Up @@ -75,8 +78,41 @@ jobs:
run: go build -o stmps-linux-${{ matrix.architecture }}

- name: Upload binary as artifact
if: matrix.go == 'stable' && matrix.os == 'ubuntu-latest'
if: matrix.os == 'ubuntu-latest' && matrix.go == 'stable' && matrix.architecture == 'amd64'
uses: actions/upload-artifact@v4
with:
path: stmps-linux-${{ matrix.architecture }}
name: stmps-linux-${{ matrix.architecture }}

screenshot:
needs: build
runs-on: ubuntu-latest

steps:
- name: Download and unpack termshot
run: |
wget https://github.com/homeport/termshot/releases/download/v${{ env.TERMSHOT_VERSION }}/termshot_${{ env.TERMSHOT_VERSION }}_linux_amd64.tar.gz
tar -xzf termshot_${{ env.TERMSHOT_VERSION }}_linux_amd64.tar.gz
chmod +x termshot
- name: Download binary from build job
uses: actions/download-artifact@v4
with:
name: stmps-linux-amd64
path: .

- name: Make binary executable and rename it
run: |
chmod +x stmps-linux-amd64
mv stmps-linux-amd64 ./stmps
- name: Run and screenshot STMPS
run: |
./termshot ./stmps --output stmps_screenshot.png
continue-on-error: true

- name: Upload screenshot
uses: actions/upload-artifact@v4
with:
name: stmps_screenshot
path: stmps_screenshot.png
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Changelog

All notable changes to this project will be documented in this file.

## [unreleased]

### 🐛 Bug Fixes

- Mpris not implementing the right interface

### ⚙️ Miscellaneous Tasks

- Rename mpris player (Player -> stmps)

### Queue

- Fix scroll behaviour, unexport some methods

<!-- generated by git-cliff -->
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.PHONY: all test changelog

all: test stmps changelog

VERSION != git describe --tags HEAD

stmps:
go build -ldflags="-X main.Version=$(VERSION)" -o stmps .

changelog:
git cliff -o CHANGELOG.md

test:
go test ./...
markdownlint README.md
golangci-lint run
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ These screenshots use [Navidrome's demo server](https://demo.navidrome.org/) ([c

Compile STMPS with `go build`. Cgo is needed for interfacing with libmpv.

STMPS can be installed without checking out the repository by running:

```bash
go install github.com/spezifisch/stmps@latest
```

### Developers & Distribution Packagers

There's a Makefile with tasks for:

- Updating the CHANGELOG.md
- Running tests & linting commands
- Compiling an executable with a derived tag for the version

These tasks depend on the following tools:

- [git-cliff](https://git-cliff.org/) for updating the CHANGELOG.md
- [markdownlint](https://github.com/igorshubovych/markdownlint-cli) for running the markdown linting test
- [golangci-lint](https://github.com/golangci/golangci-lint) for linting the Go code

## Configuration

STMPS looks for a configuration file named `stmp.toml` in either `$HOME/.config/stmp` or the directory containing the executable.
Expand Down Expand Up @@ -119,6 +139,9 @@ These controls are accessible from any view:
- `j`: Move song down in queue
- `s`: Save the queue as a playlist
- `S`: Shuffle the songs in the queue
- `l`: Load a queue previously saved to the server

When stmps exits, the queue is automatically recorded to the server, including the position in the song being played. There is a *single* queue per user that can be thusly saved. Because empty queues can not be stored on Subsonic servers, this queue is not automatically loaded; the `l` binding on the queue page will load the previous queue and seek to the last position in the top song.

If the currently playing song is moved, the music is stopped before the move, and must be re-started manually.

Expand Down Expand Up @@ -160,6 +183,8 @@ In the search field:
- `Enter`: Perform the query.
- `Escape`: Escapes into the columns, where the global key bindings work.

Note that the Search page is *not* a browser like the Browser page: it displays the search results returned by the server. Selecting a different artist will not change the album or song search results. OpenSubsonic servers implement the search function differently; in gonic, if you search for "black", you will get artists with "black" in their names in the artists column; albums with "black" in their titles in the albums column; and songs with "black" in their titles in the songs column. Navidrome appears to include all results with "black" anywhere in their IDv3 metadata. Since the API search results filteres these matches into sections -- artists, albums, and songs -- this means that, with Navidrome, you may see albums that don't have "black" in their names; maybe "black" is in their artist title.

## Advanced Configuration and Features

### MPRIS2 Integration
Expand Down
83 changes: 83 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.

[changelog]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing s
trim = true
# postprocessors
postprocessors = [
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"

[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
3 changes: 3 additions & 0 deletions gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ func InitGui(indexes *[]subsonic.SubsonicIndex,
SetTextAlign(tview.AlignLeft).
SetDynamicColors(true).
SetScrollable(false)
ui.startStopStatus.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) {
return action, nil
})

statusRight := formatPlayerStatus(0, 0, 0)
ui.playerStatus = tview.NewTextView().SetText(statusRight).
Expand Down
20 changes: 19 additions & 1 deletion gui_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package main

import (
"log"

"github.com/gdamore/tcell/v2"
"github.com/spezifisch/stmps/mpvplayer"
"github.com/spezifisch/stmps/subsonic"
Expand Down Expand Up @@ -114,10 +116,26 @@ func (ui *Ui) handlePageInput(event *tcell.EventKey) *tcell.EventKey {
func (ui *Ui) ShowPage(name string) {
ui.pages.SwitchToPage(name)
ui.menuWidget.SetActivePage(name)
_, prim := ui.pages.GetFrontPage()
ui.app.SetFocus(prim)
}

func (ui *Ui) Quit() {
// TODO savePlayQueue/getPlayQueue
if len(ui.queuePage.queueData.playerQueue) > 0 {
ids := make([]string, len(ui.queuePage.queueData.playerQueue))
for i, it := range ui.queuePage.queueData.playerQueue {
ids[i] = it.Id
}
// stmps always only ever plays the first song in the queue
pos := ui.player.GetTimePos()
if err := ui.connection.SavePlayQueue(ids, ids[0], int(pos)); err != nil {
log.Printf("error stashing play queue: %s", err)
}
} else {
// The only way to purge a saved play queue is to force an error by providing
// bad data. Therefore, we ignore errors.
_ = ui.connection.SavePlayQueue([]string{"XXX"}, "XXX", 0)
}
ui.player.Quit()
ui.app.Stop()
}
Expand Down
12 changes: 8 additions & 4 deletions help_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ k move selected song up in queue
j move selected song down in queue
s save queue as a playlist
S shuffle the current queue
l load last queue from server
`

const helpPagePlaylists = `
Expand All @@ -46,13 +47,16 @@ a add playlist or song to queue
`

const helpSearchPage = `
artist, album, or song tab
Down focus search field
artist, album, or song column
Down/Up navigate within the column
Left previous column
Right next column
Enter recursively add item to quue
a recursively add item to quue
Enter/a recursively add item to quue
/ start search
search field
Enter search for text
Esc cancel search
Note: unlike browser, columns navigate
search results, not selected items.
`
4 changes: 2 additions & 2 deletions mpvplayer/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ func (p *Player) IsSeeking() (bool, error) {
return false, nil
}

func (p *Player) SeekAbsolute(float64) error {
return nil
func (p *Player) SeekAbsolute(position int) error {
return p.instance.Command([]string{"seek", strconv.Itoa(position), "absolute"})
}

func (p *Player) Play() error {
Expand Down
8 changes: 8 additions & 0 deletions page_playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ func (p *PlaylistPage) UpdatePlaylists() {
response, err := p.ui.connection.GetPlaylists()
if err != nil {
p.logger.PrintError("GetPlaylists", err)
p.isUpdating = false
stop <- true
return
}
if response == nil {
p.logger.Printf("no error from GetPlaylists, but also no response!")
stop <- true
return
}
p.updatingMutex.Lock()
defer p.updatingMutex.Unlock()
Expand Down
35 changes: 31 additions & 4 deletions page_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,30 @@ func (ui *Ui) createQueuePage() *QueuePage {
queuePage.ui.ShowSelectPlaylist()
case 'S':
queuePage.shuffle()
case 'l':
go func() {
ssr, err := queuePage.ui.connection.LoadPlayQueue()
if err != nil {
queuePage.logger.Printf("unable to load play queue from server: %s", err)
return
}
queuePage.queueList.Clear()
queuePage.queueData.Clear()
if ssr.PlayQueue.Entries != nil {
for _, ent := range ssr.PlayQueue.Entries {
ui.addSongToQueue(&ent)
}
ui.queuePage.UpdateQueue()
if err := ui.player.Play(); err != nil {
queuePage.logger.Printf("error playing: %s", err)
}
if err = ui.player.Seek(ssr.PlayQueue.Position); err != nil {
queuePage.logger.Printf("unable to seek to position %s: %s", time.Duration(ssr.PlayQueue.Position)*time.Second, err)
}
_ = ui.player.Pause()
}
}()

default:
return event
}
Expand All @@ -121,6 +145,9 @@ func (ui *Ui) createQueuePage() *QueuePage {
// Song info
queuePage.songInfo = tview.NewTextView()
queuePage.songInfo.SetDynamicColors(true).SetScrollable(true)
queuePage.songInfo.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) {
return action, nil
})

queuePage.queueList.SetSelectionChangedFunc(queuePage.changeSelection)

Expand Down Expand Up @@ -439,12 +466,12 @@ func (q *queueData) GetColumnCount() int {
return queueDataColumns
}

var songInfoTemplateString = `[blue::b]Title:[-:-:-:-] [green::i]{{.Title}}[-:-:-:-]
var songInfoTemplateString = `[blue::b]Title:[-:-:-:-] [green::i]{{.Title}}[-:-:-:-] [yellow::i]({{formatTime .Duration}})[-:-:-:-]
[blue::b]Artist:[-:-:-:-] [::i]{{.Artist}}[-:-:-:-]
[blue::b]Album:[-:-:-:-] [::i]{{.GetAlbum}}[-:-:-:-]
[blue::b]Disc:[-:-:-:-] [::i]{{.GetDiscNumber}}[-:-:-:-]
[blue::b]Track:[-:-:-:-] [::i]{{.GetTrackNumber}}[-:-:-:-]
[blue::b]Duration:[-:-:-:-] [::i]{{formatTime .Duration}}[-:-:-:-] `
[blue::b]Disc:[-:-:-:-] [::i]{{.GetDiscNumber}}[-:-:-:-] [blue::b]Track:[-:-:-:-] [::i]{{.GetTrackNumber}}[-:-:-:-]
[blue::b]Year:[-:-:-:-] [::i]{{.GetYear}}[-:-:-:-]
`

//go:embed docs/stmps_logo.png
var _stmps_logo []byte
Loading

0 comments on commit 0346ac9

Please sign in to comment.