From faae97c47af8ed522f4d2e746f2e265bd86049c0 Mon Sep 17 00:00:00 2001 From: DECE2183 Date: Tue, 24 Dec 2024 19:43:43 +0300 Subject: [PATCH] Experimental cover drawing (by SIXEL protocol). Updated dependencies. Fixed cover encoding and mime type in cached files. --- api/api.go | 8 +- go.mod | 42 +++++----- go.sum | 107 +++++++++++++++--------- ui/components/playlist/itemdelegate.go | 2 +- ui/components/search/itemdelegate.go | 2 +- ui/components/tracker/tracker.go | 56 +++++++++++-- ui/components/tracklist/itemdelegate.go | 6 +- ui/components/tracklist/tracklist.go | 2 +- ui/model/mainPage/mainPage.go | 19 +++-- ui/model/mainPage/playControl.go | 28 ++++++- ui/style/styles.go | 7 ++ 11 files changed, 187 insertions(+), 92 deletions(-) diff --git a/api/api.go b/api/api.go index ad73aab..8765b68 100644 --- a/api/api.go +++ b/api/api.go @@ -230,20 +230,20 @@ func TrackCoverLink(track *Track, size int) string { return fmt.Sprintf("https://%s%dx%d", track.CoverUri[:len(track.CoverUri)-2], size, size) } -func DownloadTrackCover(dst io.Writer, track *Track, size int) error { +func DownloadTrackCover(dst io.Writer, track *Track, size int) (string, error) { url := TrackCoverLink(track, size) if len(url) == 0 { - return errors.New("cover not presented") + return "", errors.New("cover not presented") } resp, err := http.Get(url) if err != nil { - return err + return "", err } defer resp.Body.Close() _, err = io.Copy(dst, resp.Body) - return err + return resp.Header.Get("Content-Type"), err } func NewClient(token string) (client *YaMusicClient, err error) { diff --git a/go.mod b/go.mod index 67f6255..3ae4fab 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,21 @@ module github.com/dece2183/yamusic-tui -go 1.20 +go 1.22.0 + +toolchain go1.23.3 require ( + github.com/BourgeoisBear/rasterm v1.1.1 + github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 github.com/bogem/id3v2 v1.2.0 - github.com/charmbracelet/bubbles v0.18.0 - github.com/charmbracelet/bubbletea v0.26.6 - github.com/charmbracelet/lipgloss v0.12.1 + github.com/bogem/id3v2/v2 v2.1.4 + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.2.4 + github.com/charmbracelet/lipgloss v1.0.0 + github.com/charmbracelet/x/ansi v0.6.0 github.com/dece2183/go-stream-mp3 v1.0.0 github.com/dece2183/media-winrt-go v0.0.0-20241129214242-6a9743bbf6f5 - github.com/ebitengine/oto/v3 v3.1.0 + github.com/ebitengine/oto/v3 v3.3.1 github.com/go-ole/go-ole v1.3.0 github.com/godbus/dbus/v5 v5.1.0 github.com/quarckster/go-mpris-server v1.0.3 @@ -21,27 +27,23 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect - github.com/charmbracelet/x/ansi v0.1.4 // indirect - github.com/charmbracelet/x/input v0.1.0 // indirect - github.com/charmbracelet/x/term v0.1.1 // indirect - github.com/charmbracelet/x/windows v0.1.0 // indirect - github.com/ebitengine/purego v0.5.2 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/ebitengine/purego v0.8.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3 // indirect - golang.org/x/image v0.15.0 // indirect - golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect + golang.org/x/exp/shiny v0.0.0-20241217172543-b2144cdd0a67 // indirect + golang.org/x/image v0.23.0 // indirect + golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 0ba074b..5acc1f8 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,37 @@ +github.com/BourgeoisBear/rasterm v1.1.1 h1:J94gv2pRv+G0jXj9Pf3jUk2qQtWPCiTsiRGxlXoQvgo= +github.com/BourgeoisBear/rasterm v1.1.1/go.mod h1:Ifd+To5s/uyUiYx+B4fxhS8lUNwNLSxDBjskmC5pEyw= +github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g= +github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bogem/id3v2 v1.2.0 h1:hKDF+F1gOgQ5r1QmBCEZUk4MveJbKxCeIDSBU7CQ4oI= github.com/bogem/id3v2 v1.2.0/go.mod h1:t78PK5AQ56Q47kizpYiV6gtjj3jfxlz87oFpty8DYs8= -github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= -github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= -github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s= -github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk= +github.com/bogem/id3v2/v2 v2.1.4 h1:CEwe+lS2p6dd9UZRlPc1zbFNIha2mb2qzT1cCEoNWoI= +github.com/bogem/id3v2/v2 v2.1.4/go.mod h1:l+gR8MZ6rc9ryPTPkX77smS5Me/36gxkMgDayZ9G1vY= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= +github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= -github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= -github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= -github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= -github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= -github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= -github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= -github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4= -github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= +github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dece2183/go-stream-mp3 v1.0.0 h1:0CQqd57+Aq1ojfhoCL/+o05rq/nOQ99/CWiEo2Geg60= github.com/dece2183/go-stream-mp3 v1.0.0/go.mod h1:hDd2a9oSj7kjEpBtln+NBgxfJEMPUBguc8RH5L/trPk= github.com/dece2183/media-winrt-go v0.0.0-20241129214242-6a9743bbf6f5 h1:9CPB7CubxTytf/VTCjFHDxPso7zrQRP40bVULS9InHo= github.com/dece2183/media-winrt-go v0.0.0-20241129214242-6a9743bbf6f5/go.mod h1:nJB51Rj5RSFhiSdUrD7Sxyg2VFskoLOi/YKIOcNUh3E= -github.com/ebitengine/oto/v3 v3.1.0 h1:9tChG6rizyeR2w3vsygTTTVVJ9QMMyu00m2yBOCch6U= -github.com/ebitengine/oto/v3 v3.1.0/go.mod h1:IK1QTnlfZK2GIB6ziyECm433hAdTaPpOsGMLhEyEGTg= -github.com/ebitengine/purego v0.5.2 h1:r2MQEtkGzZ4LRtFZVAg5bjYKnUbxxloaeuGxH0t7qfs= -github.com/ebitengine/purego v0.5.2/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/ebitengine/oto/v3 v3.3.1 h1:d4McwGQuXOT0GL7bA5g9ZnaUEIEjQvG3hafzMy+T3qE= +github.com/ebitengine/oto/v3 v3.3.1/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U= +github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= +github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -37,56 +40,78 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quarckster/go-mpris-server v1.0.3 h1:ef6d3DpxlORtdEBHnhQ/j3gS0Z3+YUfXeJhC9L9DZvA= github.com/quarckster/go-mpris-server v1.0.3/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= -github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo= golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3 h1:tImqKNm/Iclm3Rqb6GffLiURSp3m1iRx/C4mturH8Ys= -golang.org/x/exp/shiny v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= -golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b h1:kfWLZgb8iUBHdE9WydD5V5dHIS/F6HjlBZNyJfn2bs4= -golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b/go.mod h1:4efzQnuA1nICq6h4kmZRMGzbPiP06lZvgADUu1VpJCE= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp/shiny v0.0.0-20241217172543-b2144cdd0a67 h1:bTeFnqCwBClQX8uFRPj1F2t9xnCyUdtJWPxCBG9KUXw= +golang.org/x/exp/shiny v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46 h1:E+R1qmJL8cmWTyWXBHVtmqRxr7FdiTwntffsba1F1Tg= +golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46/go.mod h1:Sf9LBimL0mWKEdgAjRmJ6iu7Z34osHQTK/devqFbM2I= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/ui/components/playlist/itemdelegate.go b/ui/components/playlist/itemdelegate.go index f6b5078..6c9d4a6 100644 --- a/ui/components/playlist/itemdelegate.go +++ b/ui/components/playlist/itemdelegate.go @@ -48,7 +48,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list } name := item.Name - nameLen, _ := lipgloss.Size(name) + nameLen := lipgloss.Width(name) maxLen := m.Width() - 5 if nameLen > maxLen { name = lipgloss.NewStyle().MaxWidth(maxLen-1).Render(name) + "…" diff --git a/ui/components/search/itemdelegate.go b/ui/components/search/itemdelegate.go index 5bd25bc..df8bb3e 100644 --- a/ui/components/search/itemdelegate.go +++ b/ui/components/search/itemdelegate.go @@ -37,7 +37,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list } text := string(item) - textLen, _ := lipgloss.Size(text) + textLen := lipgloss.Width(text) if textLen > maxWidth { text = text[:maxWidth-1] + "…" } else if textLen < maxWidth { diff --git a/ui/components/tracker/tracker.go b/ui/components/tracker/tracker.go index 4447029..02a783d 100644 --- a/ui/components/tracker/tracker.go +++ b/ui/components/tracker/tracker.go @@ -2,11 +2,17 @@ package tracker import ( "fmt" + "image" + "image/color/palette" + "image/draw" "io" "math" "strings" "time" + "github.com/BourgeoisBear/rasterm" + "github.com/BurntSushi/graphics-go/graphics" + "github.com/BurntSushi/graphics-go/graphics/interp" "github.com/dece2183/yamusic-tui/api" "github.com/dece2183/yamusic-tui/config" "github.com/dece2183/yamusic-tui/stream" @@ -18,6 +24,7 @@ import ( "github.com/charmbracelet/bubbles/progress" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/ansi" "github.com/ebitengine/oto/v3" ) @@ -46,6 +53,7 @@ var rewindAmount = time.Duration(config.Current.RewindDuration) * time.Second type Model struct { width int + cover string track api.Track progress progress.Model help help.Model @@ -134,11 +142,11 @@ func (m *Model) View() string { } trackAddInfo := style.TrackAddInfoStyle.Render(trackLike + trackTime) - addInfoLen, _ := lipgloss.Size(trackAddInfo) - maxLen := m.Width() - addInfoLen - 4 + addInfoLen := lipgloss.Width(trackAddInfo) + maxLen := m.Width() - addInfoLen - 4 - 14 stl := lipgloss.NewStyle().MaxWidth(maxLen - 1) - trackTitleLen, _ := lipgloss.Size(trackTitle) + trackTitleLen := lipgloss.Width(trackTitle) if trackTitleLen > maxLen { trackTitle = stl.Render(trackTitle) + "…" } else if trackTitleLen < maxLen { @@ -146,14 +154,14 @@ func (m *Model) View() string { } trackArtist := style.TrackArtistStyle.Render(helpers.ArtistList(m.track.Artists)) - trackArtistLen, _ := lipgloss.Size(trackArtist) + trackArtistLen := lipgloss.Width(trackArtist) if trackArtistLen > maxLen { trackArtist = stl.Render(trackArtist) + "…" } else if trackArtistLen < maxLen { trackArtist += strings.Repeat(" ", maxLen-trackArtistLen) } - trackTitle = lipgloss.NewStyle().Width(m.width - lipgloss.Width(trackAddInfo) - 4).Render(trackTitle) + trackTitle = lipgloss.NewStyle().Width(m.width - lipgloss.Width(trackAddInfo) - 4 - 14).Render(trackTitle) trackTitle = lipgloss.JoinHorizontal(lipgloss.Top, trackTitle, trackAddInfo) trackTitle = lipgloss.JoinVertical(lipgloss.Left, trackTitle, trackArtist, "") @@ -163,6 +171,12 @@ func (m *Model) View() string { tracker = lipgloss.JoinHorizontal(lipgloss.Top, playButton, tracker) tracker = lipgloss.JoinVertical(lipgloss.Left, tracker, trackTitle, m.help.View(helpMap)) + if len(m.cover) > 0 { + tracker = lipgloss.JoinHorizontal(lipgloss.Top, style.TrackCoverStyle.Render(m.cover), tracker) + // tracker = lipgloss.JoinHorizontal(lipgloss.Top, lipgloss.Place(14, 6, 0, 0, m.cover), tracker) + // tracker = lipgloss.JoinHorizontal(lipgloss.Top, lipgloss.PlaceHorizontal(14, 0, m.cover), tracker) + } + return style.TrackBoxStyle.Width(m.width).Render(tracker) } @@ -257,8 +271,8 @@ func (m *Model) Update(message tea.Msg) (*Model, tea.Cmd) { func (m *Model) SetWidth(width int) { m.width = width - m.progress.Width = width - 9 - m.help.Width = width - 8 + m.progress.Width = width - 9 - 14 + m.help.Width = width - 8 - 14 } func (m *Model) Width() int { @@ -291,11 +305,37 @@ func (m *Model) Volume() float64 { return m.volume } -func (m *Model) StartTrack(track *api.Track, reader *stream.BufferedStream) { +func (m *Model) StartTrack(track *api.Track, cover image.Image, reader *stream.BufferedStream) { if m.player != nil { m.Stop() } + const ( + chWidth = 12 + chHeight = 6 + pixHor = 10 + pixVer = 20 + ) + + if cover != nil { + coverScaled := image.NewPaletted(image.Rect(0, 0, chWidth*pixHor, chHeight*pixVer), palette.Plan9) + graphics.I.Scale(0.62, 0.62).TransformCenter(coverScaled, cover, interp.Bilinear) + + str := &strings.Builder{} + r := image.Rect(0, 0, chWidth*pixHor, pixVer) + coverSlice := image.NewPaletted(r, palette.Plan9) + for i := 0; i < chHeight; i++ { + draw.Draw(coverSlice, r, coverScaled, image.Pt(0, i*pixVer), draw.Src) + str.WriteString(strings.Repeat("?", chWidth)) + str.WriteString(ansi.CUB(chWidth)) + rasterm.SixelWriteImage(str, coverSlice) + str.WriteString(ansi.CUF(chWidth)) + str.WriteRune('\n') + } + + m.cover = str.String() + } + m.track = *track m.trackWrapper.NewReader(reader) m.player = m.playerContext.NewPlayer(m.trackWrapper) diff --git a/ui/components/tracklist/itemdelegate.go b/ui/components/tracklist/itemdelegate.go index a50a206..e1cbf29 100644 --- a/ui/components/tracklist/itemdelegate.go +++ b/ui/components/tracklist/itemdelegate.go @@ -68,18 +68,18 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list } trackAddInfo := style.TrackAddInfoStyle.Render(trackCache + " " + trackLike + " " + trackTime) - addInfoLen, _ := lipgloss.Size(trackAddInfo) + addInfoLen := lipgloss.Width(trackAddInfo) maxLen := m.Width() - addInfoLen - 8 stl := lipgloss.NewStyle().MaxWidth(maxLen - 1) - trackTitleLen, _ := lipgloss.Size(trackTitle) + trackTitleLen := lipgloss.Width(trackTitle) if trackTitleLen > maxLen { trackTitle = stl.Render(trackTitle) + "…" } else if trackTitleLen < maxLen { trackTitle += strings.Repeat(" ", maxLen-trackTitleLen) } - trackArtistLen, _ := lipgloss.Size(trackArtist) + trackArtistLen := lipgloss.Width(trackArtist) if trackArtistLen > maxLen { trackArtist = stl.Render(trackArtist) + "…" } else if trackArtistLen < maxLen { diff --git a/ui/components/tracklist/tracklist.go b/ui/components/tracklist/tracklist.go index 273a918..d9f850e 100644 --- a/ui/components/tracklist/tracklist.go +++ b/ui/components/tracklist/tracklist.go @@ -61,7 +61,7 @@ func (m *Model) Init() tea.Cmd { } func (m *Model) View() string { - titleLen, _ := lipgloss.Size(m.Title) + titleLen := lipgloss.Width(m.Title) if titleLen > m.width-8 { m.list.Title = lipgloss.NewStyle().MaxWidth(m.width-9).Render(m.Title) + "…" } else { diff --git a/ui/model/mainPage/mainPage.go b/ui/model/mainPage/mainPage.go index c24c827..ee8e5cc 100644 --- a/ui/model/mainPage/mainPage.go +++ b/ui/model/mainPage/mainPage.go @@ -2,7 +2,6 @@ package mainpage import ( "fmt" - "net/url" "os" "path/filepath" "time" @@ -320,14 +319,16 @@ func (m *Model) initialLoad() error { return fmt.Errorf("wrong token") } - m.client, err = api.NewClient(config.Current.Token) - if err != nil { - if _, ok := err.(*url.Error); ok { - return fmt.Errorf("unable to connect to the Yandex server") - } else { - return err - } - } + // m.client, err = api.NewClient(config.Current.Token) + // if err != nil { + // if _, ok := err.(*url.Error); ok { + // return fmt.Errorf("unable to connect to the Yandex server") + // } else { + // return err + // } + // } + + m.client = &api.YaMusicClient{} for i, station := range m.playlists.Items() { switch station.Kind { diff --git a/ui/model/mainPage/playControl.go b/ui/model/mainPage/playControl.go index 1586a8c..bf53afc 100644 --- a/ui/model/mainPage/playControl.go +++ b/ui/model/mainPage/playControl.go @@ -1,11 +1,16 @@ package mainpage import ( + "bytes" "fmt" + "image" "io" "os" - "github.com/bogem/id3v2" + _ "image/jpeg" + _ "image/png" + + "github.com/bogem/id3v2/v2" "github.com/dece2183/yamusic-tui/api" "github.com/dece2183/yamusic-tui/cache" "github.com/dece2183/yamusic-tui/stream" @@ -109,6 +114,7 @@ func (m *Model) playTrack(track *api.Track) { var ( coverFile *os.File coverStat os.FileInfo + coverType string coverBytes []byte err error ) @@ -123,7 +129,7 @@ func (m *Model) playTrack(track *api.Track) { coverStat, err = coverFile.Stat() if err != nil || coverStat.Size() == 0 { - err = api.DownloadTrackCover(coverFile, track, 200) + coverType, err = api.DownloadTrackCover(coverFile, track, 200) if err != nil { goto skipcover } @@ -170,6 +176,14 @@ skipcover: tag := id3v2.NewEmptyTag() if trackFromCache { tag.Reset(trackBuffer, id3v2.Options{Parse: true}) + pictures := tag.GetFrames("APIC") + for _, f := range pictures { + pic, ok := f.(id3v2.PictureFrame) + if !ok { + continue + } + coverBytes = pic.Picture + } } else { tag.SetDefaultEncoding(id3v2.EncodingUTF8) tag.SetTitle(track.Title) @@ -178,8 +192,9 @@ skipcover: tag.SetArtist(helpers.ArtistList(track.Artists)) tag.SetYear(fmt.Sprint(track.Albums[0].Year)) tag.AddAttachedPicture(id3v2.PictureFrame{ - MimeType: "image/jpeg", + MimeType: coverType, PictureType: id3v2.PTFrontCover, + Encoding: id3v2.EncodingUTF16BE, Picture: coverBytes, }) tag.AddFrame("TLEN", id3v2.TextFrame{ @@ -206,7 +221,12 @@ skipcover: } } - m.tracker.StartTrack(track, trackBuffer) + var cover image.Image + if len(coverBytes) > 0 { + cover, _, _ = image.Decode(bytes.NewReader(coverBytes)) + } + + m.tracker.StartTrack(track, cover, trackBuffer) m.indicateCurrentTrackPlaying(true) m.mediaHandler.OnPlayback() go m.client.PlayTrack(track, false) diff --git a/ui/style/styles.go b/ui/style/styles.go index e2c2bcb..e7cde05 100644 --- a/ui/style/styles.go +++ b/ui/style/styles.go @@ -122,3 +122,10 @@ var ( Border(lipgloss.RoundedBorder()). BorderForeground(AccentColor) ) + +var ( + TrackCoverStyle = lipgloss.NewStyle(). + MarginRight(2). + MaxHeight(6). + Width(12) +)