mirror of
https://github.com/issadarkthing/gomu.git
synced 2025-04-25 13:48:49 +08:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
08cb9c3796 | ||
![]() |
880d87eab5 | ||
![]() |
4288b08fee | ||
![]() |
50f449ec58 | ||
![]() |
e8ddb8b326 | ||
![]() |
a393aec7ab | ||
![]() |
89b3c7ef7b |
@ -134,6 +134,9 @@ I just wanted to implement my own music player with a programming language i'm c
|
||||
- pause
|
||||
- forward and rewind
|
||||
|
||||
### Similar Projects
|
||||
- [termusic](https://github.com/tramhao/termusic) (Written in rust and well maintained)
|
||||
|
||||
### Album Photo
|
||||
For songs downloaded by Gomu, the thumbnail will be embeded as Album cover. If you're not satisfied with the cover, you can edit it with kid3 and attach an image as album cover. Jpeg is tested, but other formats should work as well.
|
||||
|
||||
|
35
go.mod
35
go.mod
@ -3,35 +3,38 @@ module github.com/issadarkthing/gomu
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.6.1 // indirect
|
||||
github.com/antchfx/htmlquery v1.2.3 // indirect
|
||||
github.com/antchfx/xmlquery v1.3.4 // indirect
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
|
||||
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||
github.com/antchfx/htmlquery v1.2.6 // indirect
|
||||
github.com/antchfx/xmlquery v1.3.14 // indirect
|
||||
github.com/bogem/id3v2 v1.2.0 //indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/faiface/beep v1.0.2
|
||||
github.com/gdamore/tcell/v2 v2.2.0
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/gdamore/tcell v1.4.0 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.5.4
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gocolly/colly v1.2.0
|
||||
github.com/hajimehoshi/go-mp3 v0.3.1 // indirect
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
|
||||
github.com/hajimehoshi/oto v1.0.1 // indirect
|
||||
github.com/kennygrant/sanitize v1.2.4 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/mattn/anko v0.1.8
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598
|
||||
github.com/mattn/anko v0.1.9
|
||||
github.com/rivo/tview v0.0.0-20230104153304-892d1a2eb0da
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/temoto/robotstxt v1.1.1 // indirect
|
||||
github.com/temoto/robotstxt v1.1.2 // indirect
|
||||
github.com/tj/go-spin v1.1.0
|
||||
github.com/tramhao/id3v2 v1.2.1
|
||||
github.com/ztrue/tracerr v0.3.0
|
||||
gitlab.com/diamondburned/ueberzug-go v0.0.0-20190521043425-7c15a5f63b06
|
||||
golang.org/x/exp v0.0.0-20201229011636-eab1b5eb1a03 // indirect
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||
golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c // indirect
|
||||
golang.org/x/image v0.10.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
||||
|
187
go.sum
187
go.sum
@ -1,61 +1,68 @@
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
|
||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
|
||||
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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e h1:4ZrkT/RzpnROylmoQL57iVUL57wGKTR5O6KpVnbm2tA=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
|
||||
github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk=
|
||||
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M=
|
||||
github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
|
||||
github.com/antchfx/xmlquery v1.3.4 h1:RuhsI4AA5Ma4XoXhaAr2VjJxU0Xp0W2zy/f9ZIpsF4s=
|
||||
github.com/antchfx/xmlquery v1.3.4/go.mod h1:64w0Xesg2sTaawIdNqMB+7qaW/bSqkQm+ssPaCMWNnc=
|
||||
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/antchfx/xpath v1.1.10 h1:cJ0pOvEdN/WvYXxvRrzQH9x5QWKpzHacYO8qzCcDYAg=
|
||||
github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA=
|
||||
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/antchfx/htmlquery v1.2.6 h1:Ee7+vpVb7qbgQ4QffP6TVZrw+XMjCbth0pVKv7jqpB8=
|
||||
github.com/antchfx/htmlquery v1.2.6/go.mod h1:kYx/LosPyRriF4TVOAYmKrBgi1mfAhrwJExTcwKg530=
|
||||
github.com/antchfx/xmlquery v1.3.14 h1:JVLQF1UIstQytN6MVES7D8gCiqIazZA+A2NWryaHwYk=
|
||||
github.com/antchfx/xmlquery v1.3.14/go.mod h1:yPRBXRdd2Xqz9c2Z61qvMKbK+u3NXXydp6nqEfw4VdI=
|
||||
github.com/antchfx/xpath v1.2.2 h1:fsKX4sHfxhsGpDMYjsvCmGC0EGdiT7XA0af/6PP6Oa0=
|
||||
github.com/antchfx/xpath v1.2.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/bogem/id3v2 v1.2.0 h1:hKDF+F1gOgQ5r1QmBCEZUk4MveJbKxCeIDSBU7CQ4oI=
|
||||
github.com/bogem/id3v2 v1.2.0/go.mod h1:t78PK5AQ56Q47kizpYiV6gtjj3jfxlz87oFpty8DYs8=
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ=
|
||||
github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM=
|
||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
||||
github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4=
|
||||
github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
|
||||
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
|
||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
|
||||
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
|
||||
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
|
||||
github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKcokao=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
|
||||
github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
|
||||
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
|
||||
github.com/hajimehoshi/oto v1.0.1 h1:8AMnq0Yr2YmzaiqTg/k1Yzd6IygUGk2we9nmjgbgPn4=
|
||||
github.com/hajimehoshi/oto v1.0.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
|
||||
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
||||
@ -64,17 +71,19 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
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/anko v0.1.8 h1:wDGM0Rwgbzhk1h8xE6qAR4n+PO/9clzf3tLGtrwsqJg=
|
||||
github.com/mattn/anko v0.1.8/go.mod h1:C5D2zw4NIv/sB2SrQ3qs5wqPw0wKiA2GZqexy4ctNH0=
|
||||
github.com/mattn/anko v0.1.9 h1:OpdRDvjOY/FQXoGXkN0n7b0br4a4fHB5hhsaq0Cd3ds=
|
||||
github.com/mattn/anko v0.1.9/go.mod h1:gjrudvzf1t7FWTZo1Nbywnr75g3uDnGjXdp2nkguBjQ=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -82,95 +91,111 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/rivo/tview v0.0.0-20210312174852-ae9464cc3598 h1:AbRrGXhagPRDItERv7nauBUUPi7Ma3IGIj9FqkQKW6k=
|
||||
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598/go.mod h1:VzCN9WX13RF88iH2CaGkmdHOlsy1ZZQcTmNwROqC+LI=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/tview v0.0.0-20230104153304-892d1a2eb0da h1:3Mh+tcC2KqetuHpWMurDeF+yOgyt4w4qtLIpwSQ3uqo=
|
||||
github.com/rivo/tview v0.0.0-20230104153304-892d1a2eb0da/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA=
|
||||
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
|
||||
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||
github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/tramhao/id3v2 v1.2.1 h1:h8tXj1ReHoGwIIZEDp+fiDhGhf2wpCyrxEKLKVmhQw8=
|
||||
github.com/tramhao/id3v2 v1.2.1/go.mod h1:4jmC9bwoDhtGTsDkEBwSUlUgJq/D+8w4626jvM1Oo1k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/ztrue/tracerr v0.3.0 h1:lDi6EgEYhPYPnKcjsYzmWw4EkFEoA/gfe+I9Y5f+h6Y=
|
||||
github.com/ztrue/tracerr v0.3.0/go.mod h1:qEalzze4VN9O8tnhBXScfCrmoJo10o8TN5ciKjm6Mww=
|
||||
gitlab.com/diamondburned/ueberzug-go v0.0.0-20190521043425-7c15a5f63b06 h1:lGu8YGHgq9ABb00JDQewrqhKIvku+/1uFsnq/QUeiU8=
|
||||
gitlab.com/diamondburned/ueberzug-go v0.0.0-20190521043425-7c15a5f63b06/go.mod h1:UKSsoWKXcGQXxWYkXFB4z/UqJtIF3pZT6fHm6beLPKM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20201229011636-eab1b5eb1a03 h1:XlAInxBYX5nBofPaY51uv/x9xmRgZGr/lDOsePd2AcE=
|
||||
golang.org/x/exp v0.0.0-20201229011636-eab1b5eb1a03/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
|
||||
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f h1:kgfVkAEEQXXQ0qc6dH7n6y37NAYmTFmz0YRwrRjgxKw=
|
||||
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c h1:coiPEfMv+ThsjULRDygLrJVlNE1gDdL2g65s0LhV2os=
|
||||
golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220318055525-2edf467146b5/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.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
|
@ -1,3 +1,6 @@
|
||||
//go:build !darwin
|
||||
// +build !darwin
|
||||
|
||||
// Copyright (C) 2020 Raziman
|
||||
|
||||
package main
|
||||
|
830
playlist_darwin.go
Normal file
830
playlist_darwin.go
Normal file
@ -0,0 +1,830 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
// Copyright (C) 2020 Raziman
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
spin "github.com/tj/go-spin"
|
||||
"github.com/tramhao/id3v2"
|
||||
"github.com/ztrue/tracerr"
|
||||
|
||||
"github.com/issadarkthing/gomu/lyric"
|
||||
"github.com/issadarkthing/gomu/player"
|
||||
)
|
||||
|
||||
// Playlist struct represents playlist panel
|
||||
// that shows the tree of the music directory
|
||||
type Playlist struct {
|
||||
*tview.TreeView
|
||||
prevNode *tview.TreeNode
|
||||
defaultTitle string
|
||||
// number of downloads
|
||||
download int
|
||||
done chan struct{}
|
||||
yankFile *player.AudioFile
|
||||
}
|
||||
|
||||
func (p *Playlist) help() []string {
|
||||
|
||||
return []string{
|
||||
"j down",
|
||||
"k up",
|
||||
"h close node",
|
||||
"a create a playlist",
|
||||
"l add song to queue",
|
||||
"L add playlist to queue",
|
||||
"d delete file from filesystem",
|
||||
"D delete playlist from filesystem",
|
||||
"Y download audio from url",
|
||||
"r refresh",
|
||||
"R rename",
|
||||
"y/p yank/paste file",
|
||||
"/ find in playlist",
|
||||
"s search audio from youtube",
|
||||
"t edit mp3 tags",
|
||||
"1/2 find lyric if available",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// newPlaylist returns new instance of playlist and runs populate function
|
||||
// on root music directory.
|
||||
func newPlaylist(args Args) *Playlist {
|
||||
|
||||
anko := gomu.anko
|
||||
|
||||
m := anko.GetString("General.music_dir")
|
||||
rootDir, err := filepath.Abs(expandTilde(m))
|
||||
if err != nil {
|
||||
err = tracerr.Errorf("unable to find music directory: %e", err)
|
||||
die(err)
|
||||
}
|
||||
|
||||
// if not default value was given
|
||||
if *args.music != "~/music" {
|
||||
rootDir = expandFilePath(*args.music)
|
||||
}
|
||||
|
||||
var rootTextView string
|
||||
|
||||
useEmoji := anko.GetBool("General.use_emoji")
|
||||
|
||||
if useEmoji {
|
||||
|
||||
emojiPlaylist := anko.GetString("Emoji.playlist")
|
||||
rootTextView = fmt.Sprintf("%s %s", emojiPlaylist, path.Base(rootDir))
|
||||
|
||||
} else {
|
||||
rootTextView = path.Base(rootDir)
|
||||
}
|
||||
|
||||
root := tview.NewTreeNode(rootTextView).
|
||||
SetColor(gomu.colors.playlistDir)
|
||||
|
||||
tree := tview.NewTreeView().SetRoot(root)
|
||||
tree.SetBackgroundColor(gomu.colors.background)
|
||||
|
||||
playlist := &Playlist{
|
||||
TreeView: tree,
|
||||
defaultTitle: "─ Playlist ──┤ 0 downloads ├",
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
rootAudioFile := new(player.AudioFile)
|
||||
rootAudioFile.SetName(path.Base(rootDir))
|
||||
rootAudioFile.SetNode(root)
|
||||
rootAudioFile.SetPath(rootDir)
|
||||
|
||||
root.SetReference(rootAudioFile)
|
||||
root.SetColor(gomu.colors.playlistDir)
|
||||
|
||||
playlist.
|
||||
SetTitle(playlist.defaultTitle).
|
||||
SetBorder(true).
|
||||
SetTitleAlign(tview.AlignLeft).
|
||||
SetBorderPadding(0, 0, 1, 1)
|
||||
|
||||
populate(root, rootDir, gomu.anko.GetBool("General.sort_by_mtime"))
|
||||
|
||||
var firstChild *tview.TreeNode
|
||||
|
||||
if len(root.GetChildren()) == 0 {
|
||||
firstChild = root
|
||||
} else {
|
||||
firstChild = root.GetChildren()[0]
|
||||
}
|
||||
|
||||
playlist.setHighlight(firstChild)
|
||||
|
||||
playlist.SetChangedFunc(func(node *tview.TreeNode) {
|
||||
playlist.setHighlight(node)
|
||||
})
|
||||
|
||||
playlist.SetSelectedFunc(func(node *tview.TreeNode) {
|
||||
node.SetExpanded(!node.IsExpanded())
|
||||
})
|
||||
|
||||
cmds := map[rune]string{
|
||||
'a': "create_playlist",
|
||||
'D': "delete_playlist",
|
||||
'd': "delete_file",
|
||||
'Y': "download_audio",
|
||||
's': "youtube_search",
|
||||
'l': "add_queue",
|
||||
'L': "bulk_add",
|
||||
'h': "close_node",
|
||||
'r': "refresh",
|
||||
'R': "rename",
|
||||
'y': "yank",
|
||||
'p': "paste",
|
||||
'/': "playlist_search",
|
||||
't': "edit_tags",
|
||||
'1': "fetch_lyric",
|
||||
'2': "fetch_lyric_cn2",
|
||||
}
|
||||
|
||||
for key, cmdName := range cmds {
|
||||
src := fmt.Sprintf(`Keybinds.def_p("%c", %s)`, key, cmdName)
|
||||
anko.Execute(src)
|
||||
}
|
||||
|
||||
playlist.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
||||
|
||||
if gomu.anko.KeybindExists("playlist", e) {
|
||||
|
||||
err := gomu.anko.ExecKeybind("playlist", e)
|
||||
if err != nil {
|
||||
errorPopup(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// disable default key handler for space
|
||||
if e.Rune() == ' ' {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e
|
||||
})
|
||||
|
||||
return playlist
|
||||
}
|
||||
|
||||
// Returns the current file highlighted in the playlist
|
||||
func (p Playlist) getCurrentFile() *player.AudioFile {
|
||||
node := p.GetCurrentNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return node.GetReference().(*player.AudioFile)
|
||||
}
|
||||
|
||||
// Deletes song from filesystem
|
||||
func (p *Playlist) deleteSong(audioFile *player.AudioFile) {
|
||||
|
||||
confirmationPopup(
|
||||
"Are you sure to delete this audio file?", func(_ int, buttonName string) {
|
||||
|
||||
if buttonName == "no" || buttonName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// hehe we need to move focus to next node before delete it
|
||||
p.InputHandler()(tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), nil)
|
||||
|
||||
err := os.Remove(audioFile.Path())
|
||||
if err != nil {
|
||||
errorPopup(err)
|
||||
return
|
||||
}
|
||||
|
||||
defaultTimedPopup(" Success ",
|
||||
audioFile.Name()+"\nhas been deleted successfully")
|
||||
go gomu.app.QueueUpdateDraw(func() {
|
||||
p.refresh()
|
||||
// Here we remove the song from queue
|
||||
gomu.queue.updateQueuePath()
|
||||
gomu.queue.updateCurrentSongDelete(audioFile)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Deletes playlist/dir from filesystem
|
||||
func (p *Playlist) deletePlaylist(audioFile *player.AudioFile) (err error) {
|
||||
|
||||
// here we close the node and then move to next folder before delete
|
||||
p.InputHandler()(tcell.NewEventKey(tcell.KeyRune, 'h', tcell.ModNone), nil)
|
||||
p.InputHandler()(tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone), nil)
|
||||
|
||||
err = os.RemoveAll(audioFile.Path())
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
defaultTimedPopup(
|
||||
" Success ",
|
||||
audioFile.Name()+"\nhas been deleted successfully")
|
||||
go gomu.app.QueueUpdateDraw(func() {
|
||||
p.refresh()
|
||||
// Here we remove the song from queue
|
||||
gomu.queue.updateQueuePath()
|
||||
gomu.queue.updateCurrentSongDelete(audioFile)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bulk add a playlist to queue
|
||||
func (p *Playlist) addAllToQueue(root *tview.TreeNode) {
|
||||
|
||||
var childrens []*tview.TreeNode
|
||||
childrens = root.GetChildren()
|
||||
|
||||
// gets the parent if the highlighted item is a file
|
||||
if root.GetReference().(*player.AudioFile).IsAudioFile() {
|
||||
childrens = root.GetReference().(*player.AudioFile).ParentNode().GetChildren()
|
||||
}
|
||||
|
||||
for _, v := range childrens {
|
||||
currNode := v.GetReference().(*player.AudioFile)
|
||||
if currNode.IsAudioFile() {
|
||||
|
||||
currSong := gomu.player.GetCurrentSong()
|
||||
if currSong == nil || (currNode.Name() != currSong.Name()) {
|
||||
gomu.queue.enqueue(currNode)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Refreshes the playlist and read the whole root music dir
|
||||
func (p *Playlist) refresh() {
|
||||
|
||||
root := gomu.playlist.GetRoot()
|
||||
prevNode := gomu.playlist.GetCurrentNode()
|
||||
prevFilepath := prevNode.GetReference().(*player.AudioFile).Path()
|
||||
|
||||
root.ClearChildren()
|
||||
node := root.GetReference().(*player.AudioFile)
|
||||
|
||||
populate(root, node.Path(), gomu.anko.GetBool("General.sort_by_mtime"))
|
||||
|
||||
root.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
|
||||
// to preserve previously highlighted node
|
||||
if node.GetReference().(*player.AudioFile).Path() == prevFilepath {
|
||||
p.setHighlight(node)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Adds child while setting reference to audio file
|
||||
func (p *Playlist) addSongToPlaylist(
|
||||
audioPath string, selPlaylist *tview.TreeNode,
|
||||
) error {
|
||||
|
||||
f, err := os.Open(audioPath)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
songName := getName(audioPath)
|
||||
node := tview.NewTreeNode(songName)
|
||||
|
||||
// populateAudioLength(selPlaylist)
|
||||
audioLength, err := getTagLength(audioPath)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
audioFile := new(player.AudioFile)
|
||||
audioFile.SetName(songName)
|
||||
audioFile.SetPath(audioPath)
|
||||
audioFile.SetIsAudioFile(true)
|
||||
audioFile.SetLen(audioLength)
|
||||
audioFile.SetNode(node)
|
||||
audioFile.SetParentNode(selPlaylist)
|
||||
|
||||
displayText := setDisplayText(audioFile)
|
||||
|
||||
node.SetReference(audioFile)
|
||||
node.SetText(displayText)
|
||||
selPlaylist.AddChild(node)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gets all audio files walks from music root directory
|
||||
func (p *Playlist) getAudioFiles() []*player.AudioFile {
|
||||
|
||||
root := p.GetRoot()
|
||||
|
||||
audioFiles := []*player.AudioFile{}
|
||||
|
||||
root.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
|
||||
audioFile := node.GetReference().(*player.AudioFile)
|
||||
audioFiles = append(audioFiles, audioFile)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return audioFiles
|
||||
}
|
||||
|
||||
// Creates a directory under selected node, returns error if playlist exists
|
||||
func (p *Playlist) createPlaylist(name string) error {
|
||||
|
||||
selectedNode := p.GetCurrentNode()
|
||||
|
||||
parentNode := selectedNode.GetReference().(*player.AudioFile).ParentNode()
|
||||
|
||||
// if the current node is the root
|
||||
// sets the parent to itself
|
||||
if parentNode == nil {
|
||||
parentNode = selectedNode
|
||||
}
|
||||
|
||||
audioFile := parentNode.GetReference().(*player.AudioFile)
|
||||
|
||||
err := os.Mkdir(path.Join(audioFile.Path(), name), 0744)
|
||||
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
p.refresh()
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// This is used to replace default behaviour of SetCurrentNode which
|
||||
// adds color highlight attributes
|
||||
func (p *Playlist) setHighlight(currNode *tview.TreeNode) {
|
||||
|
||||
if p.prevNode != nil {
|
||||
if p.prevNode.GetReference().(*player.AudioFile).IsAudioFile() {
|
||||
p.prevNode.SetColor(gomu.colors.foreground)
|
||||
} else {
|
||||
p.prevNode.SetColor(gomu.colors.playlistDir)
|
||||
}
|
||||
}
|
||||
|
||||
currNode.SetColor(gomu.colors.playlistHi)
|
||||
p.SetCurrentNode(currNode)
|
||||
|
||||
p.prevNode = currNode
|
||||
}
|
||||
|
||||
// Traverses the playlist and finds the AudioFile struct
|
||||
// audioName must be hashed with sha1 first
|
||||
func (p *Playlist) findAudioFile(audioName string) (*player.AudioFile, error) {
|
||||
|
||||
root := p.GetRoot()
|
||||
|
||||
var selNode *player.AudioFile
|
||||
|
||||
root.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
|
||||
audioFile := node.GetReference().(*player.AudioFile)
|
||||
|
||||
hashed := sha1Hex(getName(audioFile.Name()))
|
||||
|
||||
if hashed == audioName {
|
||||
selNode = audioFile
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if selNode == nil {
|
||||
return nil, tracerr.New("no matching audio name")
|
||||
}
|
||||
|
||||
return selNode, nil
|
||||
}
|
||||
|
||||
func (p *Playlist) rename(newName string) error {
|
||||
|
||||
currentNode := p.GetCurrentNode()
|
||||
audio := currentNode.GetReference().(*player.AudioFile)
|
||||
|
||||
pathToFile, _ := filepath.Split(audio.Path())
|
||||
var newPath string
|
||||
if audio.IsAudioFile() {
|
||||
newPath = pathToFile + newName + ".mp3"
|
||||
} else {
|
||||
newPath = pathToFile + newName
|
||||
}
|
||||
err := os.Rename(audio.Path(), newPath)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateTitle creates a spinning motion on the title
|
||||
// of the playlist panel when downloading.
|
||||
func (p *Playlist) updateTitle() {
|
||||
|
||||
if p.download == 0 {
|
||||
p.SetTitle(p.defaultTitle)
|
||||
return
|
||||
}
|
||||
|
||||
// only one call can be made in one time
|
||||
if p.download > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
s := spin.New()
|
||||
|
||||
Download:
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-p.done:
|
||||
p.download--
|
||||
if p.download == 0 {
|
||||
p.SetTitle(p.defaultTitle)
|
||||
break Download
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
|
||||
r, g, b := gomu.colors.accent.RGB()
|
||||
hexColor := padHex(r, g, b)
|
||||
|
||||
title := fmt.Sprintf("─ Playlist ──┤ %d downloads [green]%s[#%s] ├",
|
||||
p.download, s.Next(), hexColor)
|
||||
p.SetTitle(title)
|
||||
gomu.app.Draw()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Download audio from youtube audio and adds the song to the selected playlist
|
||||
func ytdl(url string, selPlaylist *tview.TreeNode) error {
|
||||
|
||||
// lookup if youtube-dl exists
|
||||
_, err := exec.LookPath("youtube-dl")
|
||||
|
||||
if err != nil {
|
||||
defaultTimedPopup(" Error ", "youtube-dl is not in your $PATH")
|
||||
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
selAudioFile := selPlaylist.GetReference().(*player.AudioFile)
|
||||
dir := selAudioFile.Path()
|
||||
|
||||
defaultTimedPopup(" Ytdl ", "Downloading")
|
||||
|
||||
// specify the output path for ytdl
|
||||
outputDir := fmt.Sprintf(
|
||||
"%s/%%(title)s.%%(ext)s",
|
||||
dir)
|
||||
|
||||
metaData := fmt.Sprintf("%%(artist)s - %%(title)s")
|
||||
|
||||
args := []string{
|
||||
"--extract-audio",
|
||||
"--audio-format",
|
||||
"mp3",
|
||||
"--output",
|
||||
outputDir,
|
||||
"--add-metadata",
|
||||
"--embed-thumbnail",
|
||||
"--metadata-from-title",
|
||||
metaData,
|
||||
"--write-sub",
|
||||
"--all-subs",
|
||||
"--convert-subs",
|
||||
"lrc",
|
||||
url,
|
||||
}
|
||||
|
||||
cmd := exec.Command("youtube-dl", args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
gomu.playlist.download++
|
||||
go gomu.playlist.updateTitle()
|
||||
|
||||
// blocking
|
||||
err = cmd.Run()
|
||||
|
||||
gomu.playlist.done <- struct{}{}
|
||||
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
playlistPath := dir
|
||||
audioPath := extractFilePath(stdout.Bytes(), playlistPath)
|
||||
|
||||
historyPath := gomu.anko.GetString("General.history_path")
|
||||
|
||||
err = appendFile(expandTilde(historyPath), url+"\n")
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
err = gomu.playlist.addSongToPlaylist(audioPath, selPlaylist)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
// Embed lyric to mp3 as uslt
|
||||
var tag *id3v2.Tag
|
||||
tag, err = id3v2.Open(audioPath, id3v2.Options{Parse: true})
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
defer tag.Close()
|
||||
|
||||
pathToFile, _ := filepath.Split(audioPath)
|
||||
files, err := ioutil.ReadDir(pathToFile)
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
var lyricWritten int = 0
|
||||
for _, file := range files {
|
||||
fileName := file.Name()
|
||||
fileExt := filepath.Ext(fileName)
|
||||
lyricFileName := filepath.Join(pathToFile, fileName)
|
||||
if fileExt == ".lrc" {
|
||||
// Embed all lyrics and use langExt as content descriptor of uslt
|
||||
fileNameWithoutExt := strings.TrimSuffix(fileName, fileExt)
|
||||
langExt := strings.TrimPrefix(filepath.Ext(fileNameWithoutExt), ".")
|
||||
|
||||
// Read entire file content, giving us little control but
|
||||
// making it very simple. No need to close the file.
|
||||
byteContent, err := ioutil.ReadFile(lyricFileName)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
lyricContent := string(byteContent)
|
||||
|
||||
var lyric lyric.Lyric
|
||||
err = lyric.NewFromLRC(lyricContent)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
lyric.LangExt = langExt
|
||||
err = embedLyric(audioPath, &lyric, false)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
err = os.Remove(lyricFileName)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
lyricWritten++
|
||||
}
|
||||
}
|
||||
|
||||
downloadFinishedMessage := fmt.Sprintf("Finished downloading\n%s\n%v lyrics embeded", getName(audioPath), lyricWritten)
|
||||
defaultTimedPopup(" Ytdl ", downloadFinishedMessage)
|
||||
gomu.app.Draw()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add songs and their directories in Playlist panel.
|
||||
func populate(root *tview.TreeNode, rootPath string, sortMtime bool) error {
|
||||
|
||||
files, err := ioutil.ReadDir(rootPath)
|
||||
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
if sortMtime {
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
stat1 := files[i].Sys().(*syscall.Stat_t)
|
||||
stat2 := files[j].Sys().(*syscall.Stat_t)
|
||||
|
||||
time1 := time.Unix(stat1.Mtimespec.Unix())
|
||||
time2 := time.Unix(stat2.Mtimespec.Unix())
|
||||
|
||||
return time1.After(time2)
|
||||
})
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
path, err := filepath.EvalSymlinks(filepath.Join(rootPath, file.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
songName := getName(file.Name())
|
||||
child := tview.NewTreeNode(songName)
|
||||
|
||||
if file.Mode().IsRegular() {
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
filetype, err := getFileContentType(f)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip if not mp3 file
|
||||
if filetype != "mpeg" {
|
||||
continue
|
||||
}
|
||||
|
||||
audioFile := new(player.AudioFile)
|
||||
audioFile.SetName(songName)
|
||||
audioFile.SetPath(path)
|
||||
audioFile.SetIsAudioFile(true)
|
||||
audioFile.SetNode(child)
|
||||
audioFile.SetParentNode(root)
|
||||
|
||||
audioLength, err := getTagLength(audioFile.Path())
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
|
||||
audioFile.SetLen(audioLength)
|
||||
|
||||
displayText := setDisplayText(audioFile)
|
||||
|
||||
child.SetReference(audioFile)
|
||||
child.SetText(displayText)
|
||||
root.AddChild(child)
|
||||
|
||||
}
|
||||
|
||||
if file.IsDir() || file.Mode()&os.ModeSymlink != 0 {
|
||||
|
||||
audioFile := new(player.AudioFile)
|
||||
audioFile.SetName(songName)
|
||||
audioFile.SetPath(path)
|
||||
audioFile.SetIsAudioFile(false)
|
||||
audioFile.SetNode(child)
|
||||
audioFile.SetParentNode(root)
|
||||
|
||||
displayText := setDisplayText(audioFile)
|
||||
|
||||
child.SetReference(audioFile)
|
||||
child.SetColor(gomu.colors.playlistDir)
|
||||
child.SetText(displayText)
|
||||
root.AddChild(child)
|
||||
populate(child, path, sortMtime)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Playlist) yank() error {
|
||||
p.yankFile = p.getCurrentFile()
|
||||
if p.yankFile == nil {
|
||||
return errors.New("no file has been yanked")
|
||||
}
|
||||
if p.yankFile.Node() == p.GetRoot() {
|
||||
return errors.New("please don't yank the root directory")
|
||||
}
|
||||
defaultTimedPopup(" Success ", p.yankFile.Name()+"\n has been yanked successfully.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Playlist) paste() error {
|
||||
if p.yankFile == nil {
|
||||
return errors.New("no file has been yanked")
|
||||
}
|
||||
|
||||
oldAudio := p.yankFile
|
||||
oldPathDir, oldPathFileName := filepath.Split(p.yankFile.Path())
|
||||
pasteFile := p.getCurrentFile()
|
||||
var newPathDir string
|
||||
if pasteFile.IsAudioFile() {
|
||||
newPathDir, _ = filepath.Split(pasteFile.Path())
|
||||
} else {
|
||||
newPathDir = pasteFile.Path()
|
||||
}
|
||||
|
||||
if oldPathDir == newPathDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
newPathFull := filepath.Join(newPathDir, oldPathFileName)
|
||||
err := os.Rename(p.yankFile.Path(), newPathFull)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
defaultTimedPopup(" Success ", p.yankFile.Name()+"\n has been pasted to\n"+newPathDir)
|
||||
|
||||
// keep queue references updated
|
||||
newAudio := oldAudio
|
||||
newAudio.SetPath(newPathFull)
|
||||
|
||||
p.refresh()
|
||||
gomu.queue.updateQueuePath()
|
||||
if p.yankFile.IsAudioFile() {
|
||||
err = gomu.queue.updateCurrentSongName(oldAudio, newAudio)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
err = gomu.queue.updateCurrentSongPath(oldAudio, newAudio)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
p.yankFile = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDisplayText(audioFile *player.AudioFile) string {
|
||||
useEmoji := gomu.anko.GetBool("General.use_emoji")
|
||||
if !useEmoji {
|
||||
return audioFile.Name()
|
||||
}
|
||||
|
||||
if audioFile.IsAudioFile() {
|
||||
emojiFile := gomu.anko.GetString("Emoji.file")
|
||||
return fmt.Sprintf(" %s %s", emojiFile, audioFile.Name())
|
||||
}
|
||||
|
||||
emojiDir := gomu.anko.GetString("Emoji.playlist")
|
||||
return fmt.Sprintf(" %s %s", emojiDir, audioFile.Name())
|
||||
}
|
||||
|
||||
// refreshByNode is called after rename of file or folder, to refresh queue info
|
||||
func (p *Playlist) refreshAfterRename(node *player.AudioFile, newName string) error {
|
||||
|
||||
root := p.GetRoot()
|
||||
root.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
if strings.Contains(node.GetText(), newName) {
|
||||
p.setHighlight(node)
|
||||
}
|
||||
return true
|
||||
})
|
||||
// update queue
|
||||
newNode := p.getCurrentFile()
|
||||
if node.IsAudioFile() {
|
||||
err := gomu.queue.renameItem(node, newNode)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
err = gomu.queue.updateCurrentSongName(node, newNode)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
gomu.queue.updateQueuePath()
|
||||
err := gomu.queue.updateCurrentSongPath(node, newNode)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
9
queue.go
9
queue.go
@ -11,6 +11,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -354,7 +355,11 @@ func (q *Queue) shuffle() {
|
||||
func newQueue() *Queue {
|
||||
|
||||
list := tview.NewList()
|
||||
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
cacheQueuePath := filepath.Join(cacheDir, "gomu", "queue.cache")
|
||||
queue := &Queue{
|
||||
List: list,
|
||||
savedQueuePath: cacheQueuePath,
|
||||
@ -487,7 +492,7 @@ func (q *Queue) insertItem(index int, audioFile *player.AudioFile) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//update the path information in queue
|
||||
// update the path information in queue
|
||||
func (q *Queue) updateQueuePath() {
|
||||
|
||||
var songs []string
|
||||
|
35
start.go
35
start.go
@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@ -32,14 +33,7 @@ type Panel interface {
|
||||
help() []string
|
||||
}
|
||||
|
||||
// Default values for command line arguments.
|
||||
const (
|
||||
configPath = "~/.config/gomu/config"
|
||||
cacheQueuePath = "~/.local/share/gomu/queue.cache"
|
||||
musicPath = "~/music"
|
||||
)
|
||||
|
||||
// Args is the augs for gomu executable
|
||||
// Args is the args for gomu executable
|
||||
type Args struct {
|
||||
config *string
|
||||
empty *bool
|
||||
@ -48,14 +42,27 @@ type Args struct {
|
||||
}
|
||||
|
||||
func getArgs() Args {
|
||||
ar := Args{
|
||||
config: flag.String("config", configPath, "Specify config file"),
|
||||
empty: flag.Bool("empty", false, "Open gomu with empty queue. Does not override previous queue"),
|
||||
music: flag.String("music", musicPath, "Specify music directory"),
|
||||
version: flag.Bool("version", false, "Print gomu version"),
|
||||
cfd, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
logError(tracerr.Wrap(err))
|
||||
}
|
||||
configPath := filepath.Join(cfd, "gomu", "config")
|
||||
configFlag := flag.String("config", configPath, "Specify config file")
|
||||
emptyFlag := flag.Bool("empty", false, "Open gomu with empty queue. Does not override previous queue")
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
logError(tracerr.Wrap(err))
|
||||
}
|
||||
musicPath := filepath.Join(home, "Music")
|
||||
musicFlag := flag.String("music", musicPath, "Specify music directory")
|
||||
versionFlag := flag.Bool("version", false, "Print gomu version")
|
||||
flag.Parse()
|
||||
return ar
|
||||
return Args{
|
||||
config: configFlag,
|
||||
empty: emptyFlag,
|
||||
music: musicFlag,
|
||||
version: versionFlag,
|
||||
}
|
||||
}
|
||||
|
||||
// built-in functions
|
||||
|
@ -1,6 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/issadarkthing/gomu/anko"
|
||||
@ -8,6 +13,82 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Test default case
|
||||
func TestGetArgsDefaults(t *testing.T) {
|
||||
args := getArgs()
|
||||
assert.Equal(t, *args.config, "~/.config/gomu/config")
|
||||
assert.Equal(t, *args.empty, false)
|
||||
assert.Equal(t, *args.music, "~/music")
|
||||
assert.Equal(t, *args.version, false)
|
||||
}
|
||||
|
||||
// Test non-standard flags/the empty/version flags
|
||||
func TestGetArgs(t *testing.T) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Test setting config flag
|
||||
testConfig := filepath.Join(cfgDir, ".tmp", "gomu")
|
||||
_, err = os.Stat(testConfig)
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(testConfig, 0755)
|
||||
}
|
||||
defer os.RemoveAll(testConfig)
|
||||
//create a temporary config file
|
||||
tmpCfgf, err := os.CreateTemp(testConfig, "config")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
testMusic := filepath.Join(home, ".tmp", "gomu")
|
||||
_, err = os.Stat(testMusic)
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(testMusic, 0755)
|
||||
}
|
||||
defer os.RemoveAll(testMusic)
|
||||
|
||||
boolChecks := []struct {
|
||||
name string
|
||||
arg bool
|
||||
want bool
|
||||
}{
|
||||
{"empty", true, true},
|
||||
{"version", true, true},
|
||||
}
|
||||
for _, check := range boolChecks {
|
||||
t.Run("testing bool flag "+check.name, func(t *testing.T) {
|
||||
flag.CommandLine.Set(check.name, strconv.FormatBool(check.arg))
|
||||
flag.CommandLine.Parse(os.Args[1:])
|
||||
assert.Equal(t, check.arg, check.want)
|
||||
})
|
||||
}
|
||||
strChecks := []struct {
|
||||
name string
|
||||
arg string
|
||||
want string
|
||||
}{
|
||||
{"config", tmpCfgf.Name(), tmpCfgf.Name()},
|
||||
{"music", testMusic, testMusic},
|
||||
}
|
||||
for _, check := range strChecks {
|
||||
t.Run("testing string flag "+check.name, func(t *testing.T) {
|
||||
flag.CommandLine.Set(check.name, check.arg)
|
||||
flag.CommandLine.Parse(os.Args[1:])
|
||||
fmt.Println("flag value: ", check.arg)
|
||||
assert.Equal(t, check.arg, check.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupHooks(t *testing.T) {
|
||||
|
||||
gomu := newGomu()
|
||||
|
Loading…
x
Reference in New Issue
Block a user