reupload after migration
This commit is contained in:
parent
9478f29c29
commit
5639101bac
|
@ -1,13 +1,15 @@
|
|||
# ---> Go
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
Sonically
|
||||
*Sonically
|
||||
Sonically*
|
||||
*Sonically.debug
|
||||
Sonically.debug*
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
@ -18,6 +20,11 @@
|
|||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
# Personal data dir dynamicly created by App
|
||||
.Sonically/**
|
||||
.Sonically
|
||||
|
||||
# Build Environment Test and Backup Dir
|
||||
build_environment/backup
|
||||
build_environment/test
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
platform: linux/arm64
|
||||
pipeline:
|
||||
build:
|
||||
image: golang:alpine
|
||||
when:
|
||||
event: tag
|
||||
environment:
|
||||
GOOS: linux
|
||||
GOARCH: arm64
|
||||
CGO_ENABLED: "1"
|
||||
commands:
|
||||
- "apk -U upgrade"
|
||||
- "apk add \
|
||||
protobuf-dev \
|
||||
musl-dev \
|
||||
protoc \
|
||||
build-base \
|
||||
pkgconfig \
|
||||
gtk4.0-dev \
|
||||
glib-dev \
|
||||
gdk-pixbuf-dev \
|
||||
opusfile-dev \
|
||||
libnotify-dev \
|
||||
portaudio-dev \
|
||||
gobject-introspection-dev"
|
||||
- "go build -tags netgo -o sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64"
|
||||
- "sha512sum sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64-sha512.sum"
|
||||
- "sha256sum sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64-sha256.sum"
|
||||
- "md5sum sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64-md5.sum"
|
||||
publish:
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
base_url: https://git.itmodulo.eu
|
||||
api_key:
|
||||
from_secret: gitea_akey
|
||||
files: sonically_linux_musl*
|
||||
when:
|
||||
event: tag
|
|
@ -0,0 +1,40 @@
|
|||
platform: linux/amd64
|
||||
pipeline:
|
||||
build:
|
||||
image: debian:bookworm
|
||||
when:
|
||||
event: tag
|
||||
environment:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
CGO_ENABLED: "1"
|
||||
PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/go/bin
|
||||
commands:
|
||||
- "apt-get update && apt-get upgrade -y && apt-get install wget git -y"
|
||||
- "wget -q https://go.dev/dl/go1.18.3.linux-amd64.tar.gz && tar -zxf go1.18.3.linux-amd64.tar.gz -C /usr/local/ && go version"
|
||||
- "apt-get install -y \
|
||||
build-essential \
|
||||
protobuf-compiler \
|
||||
gcc \
|
||||
upx \
|
||||
pkg-config \
|
||||
libgtk-4-dev \
|
||||
libglib2.0-dev \
|
||||
libopusfile-dev \
|
||||
libnotify-dev \
|
||||
libportaudio2 \
|
||||
portaudio19-dev \
|
||||
libgraphene-1.0-dev"
|
||||
- "go build -tags netgo -o sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64"
|
||||
- "sha512sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64-sha512.sum"
|
||||
- "sha256sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64-sha256.sum"
|
||||
- "md5sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64-md5.sum"
|
||||
publish:
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
base_url: https://git.itmodulo.eu
|
||||
api_key:
|
||||
from_secret: gitea_akey
|
||||
files: sonically_linux_glibc*
|
||||
when:
|
||||
event: tag
|
|
@ -0,0 +1,38 @@
|
|||
platform: linux/amd64
|
||||
pipeline:
|
||||
build:
|
||||
image: golang:alpine
|
||||
when:
|
||||
event: tag
|
||||
environment:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
CGO_ENABLED: "1"
|
||||
commands:
|
||||
- "apk -U upgrade"
|
||||
- "apk add \
|
||||
protobuf-dev \
|
||||
musl-dev \
|
||||
protoc \
|
||||
build-base \
|
||||
pkgconfig \
|
||||
gtk4.0-dev \
|
||||
glib-dev \
|
||||
gdk-pixbuf-dev \
|
||||
opusfile-dev \
|
||||
libnotify-dev \
|
||||
portaudio-dev \
|
||||
gobject-introspection-dev"
|
||||
- "go build -tags netgo -o sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64"
|
||||
- "sha512sum sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64-sha512.sum"
|
||||
- "sha256sum sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64-sha256.sum"
|
||||
- "md5sum sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64-md5.sum"
|
||||
publish:
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
base_url: https://git.itmodulo.eu
|
||||
api_key:
|
||||
from_secret: gitea_akey
|
||||
files: sonically_linux_musl*
|
||||
when:
|
||||
event: tag
|
|
@ -0,0 +1,40 @@
|
|||
platform: linux/arm64
|
||||
pipeline:
|
||||
build:
|
||||
image: debian:bookworm
|
||||
when:
|
||||
event: tag
|
||||
environment:
|
||||
GOOS: linux
|
||||
GOARCH: arm64
|
||||
CGO_ENABLED: "1"
|
||||
PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/go/bin
|
||||
commands:
|
||||
- "apt-get update && apt-get upgrade -y && apt-get install wget git -y"
|
||||
- "wget -q https://go.dev/dl/go1.18.3.linux-arm64.tar.gz && tar -zxf go1.18.3.linux-arm64.tar.gz -C /usr/local/ && go version"
|
||||
- "apt-get install -y \
|
||||
build-essential \
|
||||
protobuf-compiler \
|
||||
gcc \
|
||||
upx \
|
||||
pkg-config \
|
||||
libgtk-4-dev \
|
||||
libglib2.0-dev \
|
||||
libopusfile-dev \
|
||||
libnotify-dev \
|
||||
libportaudio2 \
|
||||
portaudio19-dev \
|
||||
libgraphene-1.0-dev"
|
||||
- "go build -tags netgo -o sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64"
|
||||
- "sha512sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64-sha512.sum"
|
||||
- "sha256sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64-sha256.sum"
|
||||
- "md5sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64-md5.sum"
|
||||
publish:
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
base_url: https://git.itmodulo.eu
|
||||
api_key:
|
||||
from_secret: gitea_akey
|
||||
files: sonically_linux_glibc*
|
||||
when:
|
||||
event: tag
|
30
README.md
30
README.md
|
@ -1,3 +1,29 @@
|
|||
# Sonically
|
||||
![Sonically logo](https://static.itmodulo.eu/files/l/sonically.svg)
|
||||
|
||||
GUI native Subsonic API client optimized for mobile outdoor usage on *nix platforms like pmOS, Mobian.
|
||||
# Sonically [BETA]
|
||||
|
||||
## Status
|
||||
***!!! SUSPENDED !!!***
|
||||
|
||||
I was forced to go back to Android, Sonically is not priority there.
|
||||
I'm looking forwared to switch to pmOS again, I'll probably do so in the mid of 2023 and development will restart ;)
|
||||
|
||||
## Info
|
||||
1. **Priority**: GTK4 is cool but not ideal, especially in go. I'll switch to something else. Probably native (for go) Fyne UI Toolkit. This should unlock seeking.
|
||||
2. **Priority**: Clean code
|
||||
3. Proxy settings, Lyrics self-fetching will be removed. This is non-goal now. You self-host you trust your server, you trust this app as it **should** connect only to this server. If you want more goodies, use advanced server implementation.
|
||||
4. Since upcoming fix [*read important below]* I'll start using branches, at least 2, master and develop.
|
||||
5. Packaging probably own repo.
|
||||
|
||||
## Installation
|
||||
I lost somwhere working all binaries from 2022. Forgive me that.
|
||||
Please compile yourself for now.
|
||||
|
||||
**Important**. First commit on this repo probably won't compile as it depends on LyricsApiGoExtneded that I don't longer care, also fscache may be broken. I'll try to fix that in the meantime.
|
||||
|
||||
**Hint** You may look into APKBUILD if you use pmOS, it should contain reciepe for building apkand
|
||||
|
||||
## Configuration
|
||||
Ensure you have keyring named login. If no create it using e.g. seahorse.
|
||||
|
||||
Recommended settings are opus 128, buffer 4096 (probably only working, as some values may be hardcoded)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
![Sonically logo](https://static.itmodulo.eu/files/l/sonically.svg)
|
||||
|
||||
# Sonically [BETA]
|
||||
|
||||
## Status
|
||||
***!!! SUSPENDED !!!***
|
||||
|
||||
I was forced to go back to Android, Sonically is not priority there.
|
||||
I'm looking forwared to switch to pmOS again, I'll probably do so in the mid of 2023 and development will restart ;)
|
||||
|
||||
## Info
|
||||
1. **Priority**: GTK4 is cool but not ideal, especially in go. I'll switch to something else. Probably native (for go) Fyne UI Toolkit. This should unlock seeking.
|
||||
2. **Priority**: Clean code
|
||||
3. Proxy settings, Lyrics self-fetching will be removed. This is non-goal now. You self-host you trust your server, you trust this app as it **should** connect only to this server. If you want more goodies, use advanced server implementation.
|
||||
4. Since upcoming fix [*read important below]* I'll start using branches, at least 2, master and develop.
|
||||
5. Packaging probably own repo.
|
||||
|
||||
## Installation
|
||||
I lost somwhere working all binaries from 2022. Forgive me that.
|
||||
Please compile yourself for now.
|
||||
|
||||
**Important**. First commit on this repo probably won't compile as it depends on LyricsApiGoExtneded that I don't longer care, also fscache may be broken. I'll try to fix that in the meantime.
|
||||
|
||||
**Hint** You may look into APKBUILD if you use pmOS, it should contain reciepe for building apkand
|
||||
|
||||
## Configuration
|
||||
Ensure you have keyring named login. If no create it using e.g. seahorse.
|
||||
|
||||
Recommended settings are opus 128, buffer 4096 (probably only working, as some values may be hardcoded)
|
|
@ -0,0 +1,381 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"time"
|
||||
// "math"
|
||||
"strconv"
|
||||
|
||||
"git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended"
|
||||
"github.com/TheCreeper/go-notify"
|
||||
// "github.com/disintegration/imaging"
|
||||
"github.com/diamondburned/gotk4/pkg/core/glib"
|
||||
// "github.com/djherbis/fscache"
|
||||
"github.com/gordonklaus/portaudio"
|
||||
// "github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
// "github.com/delucks/go-subsonic"
|
||||
"github.com/hraban/opus"
|
||||
)
|
||||
|
||||
const rate float64 = 48000
|
||||
const channels uint8 = 2
|
||||
|
||||
var PlayAudio bool = true // Used for play pause
|
||||
var EndPlayer bool = false // Used for ending recursion
|
||||
var StreamChanged bool = false
|
||||
|
||||
func PlayPort() {
|
||||
|
||||
nodeID := MainQueue[NowPlayingIndex]
|
||||
songdata, _ := CurrentClient.GetSong(nodeID)
|
||||
albmdata, _ := CurrentClient.GetAlbum(songdata.AlbumID)
|
||||
|
||||
// go glib.IdleAdd(FindSubtitles(songdata.Artist, songdata.Title))
|
||||
TotalTime = songdata.Duration
|
||||
|
||||
// Async Gui update needed by GTK
|
||||
|
||||
var SetPlayingGuiData = func() {
|
||||
MainArtistLabel.SetLabel(songdata.Artist)
|
||||
MainSongTitleLabel.SetLabel(songdata.Title)
|
||||
MainAlbumLabel.SetLabel(songdata.Album)
|
||||
MainTotalTimeLabel.SetLabel(DurationToReadable(songdata.Duration))
|
||||
|
||||
DownloadCoverArtFullSize(albmdata.CoverArt)
|
||||
var imgpath string = GetRunDir() + MainDir + DataDir + "/" + albmdata.CoverArt + ".jpg"
|
||||
MainAlbumCover.SetFromFile(imgpath)
|
||||
|
||||
// path2 := os.UserHomeDir + MainDir + DataDir + "/" + albmdata.CoverArt + "-b.jpg"
|
||||
|
||||
// blu, err := os.Open(imgpath)
|
||||
// if err != nil {
|
||||
// LogOnError(err)
|
||||
// }
|
||||
// defer blu.Close()
|
||||
|
||||
// img, _, err := image.Decode(blu)
|
||||
// if err != nil {
|
||||
// LogOnError(err)
|
||||
// }
|
||||
|
||||
// dstImage := imaging.Blur(img, MainSettings.BlurRadius)
|
||||
// err = imaging.Save(dstImage, path2)
|
||||
// if err != nil {
|
||||
// LogOnError(err)
|
||||
// }
|
||||
|
||||
// AD1
|
||||
// MainAlbumCover.SetFromPixbuf(pixbuf)
|
||||
|
||||
MainPlayPauseButton.SetLabel("❚❚")
|
||||
}
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt, os.Kill)
|
||||
// param := map[string]string{
|
||||
// "maxBitRate": strconv.Itoa(int(MainSettings.Bitrate)),
|
||||
// "MainFormat": MainSettings.Codec,
|
||||
// "transcodings": MainSettings.Codec,
|
||||
// "estimateContentLength": "true",
|
||||
// }
|
||||
portaudio.Initialize()
|
||||
defer portaudio.Terminate()
|
||||
|
||||
// create the cache, keys expire after 1 hour.
|
||||
|
||||
// var ReadMaster fscache.ReadAtCloser
|
||||
// var WriteMaster io.WriteCloser
|
||||
|
||||
// IsCached := CurrentCache.Exists(nodeID)
|
||||
|
||||
ReadMaster, _, _ = CurrentCache.Get(nodeID)
|
||||
|
||||
// if !IsCached {
|
||||
// reader, _ := CurrentClient.Stream(nodeID, param)
|
||||
// go Backgroundread(reader, WriteMaster)
|
||||
// } else {
|
||||
// fmt.Println("Playing ", songdata.Artist, " - ", songdata.Title, " from cache")
|
||||
// }
|
||||
|
||||
// Writing needs to be implemented in diffrenet place
|
||||
// Here we use only reader
|
||||
|
||||
//AD2
|
||||
|
||||
OpusStr, err := opus.NewStream(ReadMaster)
|
||||
chk(err)
|
||||
result := <-AudioControlChan
|
||||
fmt.Println("Result of channel is: ", result)
|
||||
CurrentStream, _ := portaudio.OpenDefaultStream(0, 2, rate, portaudio.FramesPerBufferUnspecified, &Out)
|
||||
// LogOnError(err)
|
||||
EndPlayer = false
|
||||
StreamChanged = true
|
||||
PlayAudio = true
|
||||
MainNotification := notify.NewNotification("Playing", songdata.Artist+": "+songdata.Title)
|
||||
MainNotification.Timeout = 2000
|
||||
if _, err := MainNotification.Show(); err != nil {
|
||||
return
|
||||
}
|
||||
go glib.IdleAdd(SetPlayingGuiData)
|
||||
MainAdjustment.Configure(float64(0), float64(0), float64(1), float64(1), float64(10), float64(0))
|
||||
// chk(CurrentStream.Start())
|
||||
|
||||
for !EndPlayer {
|
||||
if PlayAudio {
|
||||
if StreamChanged {
|
||||
CurrentStream.Start()
|
||||
StreamChanged = false
|
||||
}
|
||||
_, err := OpusStr.Read(Out)
|
||||
DrawnTime += float64(1920 / rate / 2)
|
||||
glib.IdleAdd(UpdatePBar())
|
||||
if (err == io.EOF) || (err == io.ErrUnexpectedEOF) || (TimeInPrecent >= 1.01) || (err == portaudio.StreamIsStopped) || (err == portaudio.TimedOut) {
|
||||
fmt.Println("EOF", NowPlayingIndex, len(MainQueue)) // TODO Stram recover when unexpected
|
||||
err = CurrentStream.Stop()
|
||||
glib.IdleAdd(SetZeroTime())
|
||||
defer CurrentStream.Close()
|
||||
Out = make([]int16, 1920)
|
||||
notify.CloseNotification(MainNotification.ReplacesID)
|
||||
if NowPlayingIndex+1 < len(MainQueue) {
|
||||
fmt.Println("Playing next song")
|
||||
NowPlayingIndex++
|
||||
go StartCaching(MainQueue[NowPlayingIndex])
|
||||
if NowPlayingIndex+1 <= len(MainQueue)-1 {
|
||||
// fmt.Println(songdata.Title, " +1 needs to be downloaded")
|
||||
go CachingWaiter(MainQueue[NowPlayingIndex], MainQueue[NowPlayingIndex+1], 5)
|
||||
}
|
||||
if NowPlayingIndex+2 <= len(MainQueue)-1 {
|
||||
// fmt.Println(songdata.Title, " +2 needs to be downloaded")
|
||||
go CachingWaiter(MainQueue[NowPlayingIndex+1], MainQueue[NowPlayingIndex+2], 5)
|
||||
}
|
||||
go PlayPort() // Run new gouroutine to let func complete
|
||||
EndPlayer = true
|
||||
break
|
||||
} else if (NowPlayingIndex + 1) == len(MainQueue) {
|
||||
fmt.Println("End playing")
|
||||
CurrentStream.Close()
|
||||
EndPlayer = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// if (err != portaudio.OutputUnderflowed && err != nil) {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
err := CurrentStream.Write()
|
||||
if err != nil {
|
||||
// LogOnError(err)
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
StreamChanged = true
|
||||
CurrentStream.Stop()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-sig:
|
||||
CurrentStream.Close()
|
||||
EndPlayer = true
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
PlayAudio = false
|
||||
glib.IdleAdd(SetZeroTime())
|
||||
fmt.Println("End playport")
|
||||
AudioControlChan <- true
|
||||
}
|
||||
func Backgroundread(reader io.Reader, w io.WriteCloser, id string) {
|
||||
fmt.Println("DEBUG: backgroundread ", id)
|
||||
DownloadSyncGroup.Add(1)
|
||||
readBuffer := make([]byte, MainSettings.DownladBufferSize)
|
||||
for {
|
||||
n, err := reader.Read(readBuffer)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
w.Write(readBuffer[:n])
|
||||
break
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
w.Write(readBuffer[:n])
|
||||
}
|
||||
w.Close()
|
||||
runtime.GC()
|
||||
DownloadSyncGroup.Done()
|
||||
fmt.Println("Ended downloading ", id)
|
||||
CachingMap[id] = true
|
||||
}
|
||||
|
||||
func CachingWaiter(idBlocking string, idNext string, waitTime int) {
|
||||
isDownloaded, exists := CachingMap[idBlocking]
|
||||
if !isDownloaded || !exists {
|
||||
if waitTime == 0 {
|
||||
waitTime = 3
|
||||
}
|
||||
time.Sleep(time.Duration(waitTime) * time.Second)
|
||||
CachingWaiter(idBlocking, idNext, waitTime-1)
|
||||
} else if isDownloaded && exists {
|
||||
go StartCaching(idNext)
|
||||
} else if !isDownloaded && exists {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func StartCaching(songID string) {
|
||||
|
||||
// songdata, _ := CurrentClient.GetSong(songID)
|
||||
// albmdata, _ := CurrentClient.GetAlbum(songdata.AlbumID)
|
||||
|
||||
param := map[string]string{
|
||||
"maxBitRate": strconv.Itoa(int(MainSettings.Bitrate)),
|
||||
"MainFormat": MainSettings.Codec,
|
||||
"transcodings": MainSettings.Codec,
|
||||
"estimateContentLength": "true",
|
||||
}
|
||||
|
||||
cacheExists := CurrentCache.Exists(songID)
|
||||
_, WriteMaster, _ = CurrentCache.Get(songID)
|
||||
|
||||
if !cacheExists {
|
||||
reader, _ := CurrentClient.Stream(songID, param)
|
||||
// go DownloadCoverArtFullSize(albmdata.CoverArt)
|
||||
Backgroundread(reader, WriteMaster, songID)
|
||||
} else {
|
||||
fmt.Println("Playing from cache")
|
||||
}
|
||||
}
|
||||
|
||||
// GTK Async gui update
|
||||
func UpdatePBar() func() {
|
||||
var up = func() {
|
||||
TimeInPrecent = float64(DrawnTime / float64(TotalTime))
|
||||
MainTimeLeft.SetText(DurationToReadable(TotalTime - int(float64(DrawnTime))))
|
||||
MainTimeDrawnLabel.SetText(DurationToReadable(int(float64(DrawnTime))))
|
||||
MainAdjustment.SetValue(float64(DrawnTime / float64(TotalTime)))
|
||||
}
|
||||
return up
|
||||
}
|
||||
|
||||
// GTK Async gui update
|
||||
func SetZeroTime() func() {
|
||||
TotalTime = 0
|
||||
DrawnTime = 0
|
||||
TimeInPrecent = 0.0
|
||||
var up = func() {
|
||||
MainPlayPauseButton.SetLabel("▶")
|
||||
MainTimeLeft.SetText(":")
|
||||
MainTimeDrawnLabel.SetText(":")
|
||||
MainAdjustment.SetValue(TimeInPrecent)
|
||||
MainTotalTimeLabel.SetLabel(":")
|
||||
MainArtistLabel.SetLabel(":")
|
||||
MainSongTitleLabel.SetLabel(":")
|
||||
MainAlbumLabel.SetLabel(":")
|
||||
|
||||
}
|
||||
return up
|
||||
}
|
||||
|
||||
func chk(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func DurationToReadable(dur int) string {
|
||||
|
||||
min := dur / 60
|
||||
sec := dur % 60
|
||||
|
||||
if min >= 60 {
|
||||
hr := dur / 3600
|
||||
min = (dur - (hr * 3600)) / 60
|
||||
sec = (dur - (hr * 3600) - (min * 60))
|
||||
return (PrefixZeroLook(hr) + ":" + PrefixZeroLook(min) + ":" + PrefixZeroLook(sec))
|
||||
}
|
||||
return (PrefixZeroLook(min) + ":" + PrefixZeroLook(sec))
|
||||
|
||||
}
|
||||
|
||||
func PrefixZeroLook(localInt int) string {
|
||||
if localInt < 10 {
|
||||
return ("0" + strconv.Itoa(localInt))
|
||||
} else {
|
||||
return strconv.Itoa(localInt)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Exists(name string) bool {
|
||||
_, err := os.Stat(name)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func FindSubtitles(artist string, song string) func() {
|
||||
// TODO Repair
|
||||
var fun = func() {
|
||||
l := lyrics.New(*MetadataClient, lyrics.WithAllProviders())
|
||||
lyric, err := l.Search(artist, song)
|
||||
buf := MainSubtitlesTextView.Buffer()
|
||||
if err != nil {
|
||||
fmt.Printf("Lyrics for %v-%v were not found", artist, song)
|
||||
buf.SetText("Not found")
|
||||
}
|
||||
buf.SetText(lyric)
|
||||
MainSubtitlesTextView.SetBuffer(buf)
|
||||
}
|
||||
return fun
|
||||
|
||||
}
|
||||
|
||||
// AD1
|
||||
// pixbuf2, err = gdk.PixbufNewFromFileAtSize(path2, w, h)
|
||||
|
||||
// gtk.LayoutNew()
|
||||
|
||||
// var css string = `
|
||||
// GtkWindow {
|
||||
// background-color: red;
|
||||
// background-repeat: no-repeat;
|
||||
// background-size: cover;
|
||||
// background-position: center center;
|
||||
// `
|
||||
|
||||
// var csscustom = `background-image: url("` + imgpath + `");
|
||||
// }`
|
||||
// var csscustom = `background-size: cover; background-color: red;
|
||||
// }`
|
||||
// fmt.Println(css + csscustom)
|
||||
// prov, err := gtk.CssProviderNew()
|
||||
// LogOnError(err)
|
||||
// err = prov.LoadFromData(css + csscustom)
|
||||
// LogOnError(err)
|
||||
// scrn, err := gdk.ScreenGetDefault()
|
||||
// LogOnError(err)
|
||||
// gtk.AddProviderForScreen(scrn, prov, 1)
|
||||
// MainWindow.ShowAll()
|
||||
// LogFatalOnError(err)
|
||||
|
||||
// AD2
|
||||
// path := os.UserHomeDir + MainDir + DataDir + "/" + nodeID.(string) + ".opus"
|
||||
// var OpusStr *opus.Stream
|
||||
// if !Exists(path) {
|
||||
// f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
// chk(err)
|
||||
// tee := io.TeeReader(reader, f)
|
||||
// OpusStr, err = opus.NewStream(tee)
|
||||
// } else {
|
|
@ -0,0 +1,24 @@
|
|||
package main
|
||||
|
||||
import "log"
|
||||
|
||||
func PanicOnError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func LogFatalOnError(err error) {
|
||||
if err != nil {
|
||||
// log.Fatalln(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func LogOnError(err error) {
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
// panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
module git.itmodulo.eu/LinuxOnMobile/Sonically
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended v0.1.45-0.20220502211740-bdde16173c5c
|
||||
github.com/TheCreeper/go-notify v0.2.0
|
||||
github.com/delucks/go-subsonic v0.0.0-20220623171311-8b0662f9f006
|
||||
github.com/diamondburned/gotk4/pkg v0.0.0-20220529201008-66c7fe5d2b7c
|
||||
github.com/djherbis/fscache v0.10.1
|
||||
github.com/gordonklaus/portaudio v0.0.0-20220320131553-cc649ad523c1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/hraban/opus v0.0.0-20220302220929-eeacdbcb92d0
|
||||
github.com/zalando/go-keyring v0.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gosimple/slug v1.12.0 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
|
||||
gopkg.in/djherbis/atime.v1 v1.0.0 // indirect
|
||||
gopkg.in/djherbis/stream.v1 v1.3.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
|
@ -0,0 +1,87 @@
|
|||
git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended v0.1.45-0.20220502211740-bdde16173c5c h1:rMlCwAUtUci5aHwQ/vnN8x5k6veHEQ39qstNTBqDZOg=
|
||||
git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended v0.1.45-0.20220502211740-bdde16173c5c/go.mod h1:UvHix+BErO9l3dealuksQRc2RYWXSCqxUo3HurXPGvs=
|
||||
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/TheCreeper/go-notify v0.2.0 h1:akzlSD8IWx+uOZqGNwS0FYsThYuw11JkXsYiQXY5kgo=
|
||||
github.com/TheCreeper/go-notify v0.2.0/go.mod h1:paZnY8fMbaOyZLQWJitGWAMrO5ot3Ow7id47cyEL1KA=
|
||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
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/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||
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/delucks/go-subsonic v0.0.0-20220623171311-8b0662f9f006 h1:hFXcR3r0Vsnr+mMMwMrgm+mlAR18k/AuuEvNuMbNuSQ=
|
||||
github.com/delucks/go-subsonic v0.0.0-20220623171311-8b0662f9f006/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo=
|
||||
github.com/diamondburned/gotk4/pkg v0.0.0-20220529201008-66c7fe5d2b7c h1:d09K0Y9HQ6/WLlFlH11dPt9TdDLIOxlztHaLUOpKa04=
|
||||
github.com/diamondburned/gotk4/pkg v0.0.0-20220529201008-66c7fe5d2b7c/go.mod h1:rLH6FHos690jFgAM/GYEpMykuE/9NmN6zOvFlr8JTvE=
|
||||
github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk=
|
||||
github.com/djherbis/fscache v0.10.1/go.mod h1:yyPYtkNnnPXsW+81lAcQS6yab3G2CRfnPLotBvtbf0c=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
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/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gordonklaus/portaudio v0.0.0-20220320131553-cc649ad523c1 h1:FgUJ91JoMbS5qWXdIpnHta1hLtw1X8n2ek5JRED3R1I=
|
||||
github.com/gordonklaus/portaudio v0.0.0-20220320131553-cc649ad523c1/go.mod h1:HfYnZi/ARQKG0dwH5HNDmPCHdLiFiBf+SI7DbhW7et4=
|
||||
github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc=
|
||||
github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hraban/opus v0.0.0-20220302220929-eeacdbcb92d0 h1:kWEAL53h9DdQ2Utz2vKhgLutpSS1L6WDB37xv1VMKwU=
|
||||
github.com/hraban/opus v0.0.0-20220302220929-eeacdbcb92d0/go.mod h1:YQQXrWHN3JEvCtw5ImyTCcPeU/ZLo/YMA+TpB64XdrU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
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/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs=
|
||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
|
||||
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/djherbis/atime.v1 v1.0.0 h1:eMRqB/JrLKocla2PBPKgQYg/p5UG4L6AUAs92aP7F60=
|
||||
gopkg.in/djherbis/atime.v1 v1.0.0/go.mod h1:hQIUStKmJfvf7xdh/wtK84qe+DsTV5LnA9lzxxtPpJ8=
|
||||
gopkg.in/djherbis/stream.v1 v1.3.1 h1:uGfmsOY1qqMjQQphhRBSGLyA9qumJ56exkRu9ASTjCw=
|
||||
gopkg.in/djherbis/stream.v1 v1.3.1/go.mod h1:aEV8CBVRmSpLamVJfM903Npic1IKmb2qS30VAZ+sssg=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,116 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// "github.com/delucks/go-subsonic"
|
||||
// "fmt"
|
||||
"github.com/diamondburned/gotk4/pkg/core/glib"
|
||||
)
|
||||
|
||||
func DownloadPlaylist() {
|
||||
for child := PlayListListBox.FirstChild(); child != nil; {
|
||||
PlayListListBox.Remove(child)
|
||||
child = PlayListListBox.FirstChild()
|
||||
}
|
||||
param := map[string]string{}
|
||||
playlists, err := CurrentClient.GetPlaylists(param)
|
||||
LogOnError(err)
|
||||
|
||||
for i, item := range playlists {
|
||||
playlist, _ := CurrentClient.GetPlaylist(item.ID)
|
||||
PlaylistCached = append(PlaylistCached, item.ID)
|
||||
|
||||
pat := GetRunDir() + MainDir + InfDir + "/" + item.ID
|
||||
SerializePlaylist(*playlist, pat)
|
||||
|
||||
var upstream = func() {
|
||||
CreateImagelessPlaylistListBoxRow(PlayListListBox, playlist.Name, DurationToReadable(playlist.Duration), i, playlist.Entry)
|
||||
}
|
||||
glib.IdleAdd(upstream)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPlaylist() {
|
||||
|
||||
for child := PlayListListBox.FirstChild(); child != nil; {
|
||||
PlayListListBox.Remove(child)
|
||||
child = PlayListListBox.FirstChild()
|
||||
}
|
||||
|
||||
for i, id := range PlaylistCached {
|
||||
// fmt.Printf("{}",item)
|
||||
|
||||
item := DeSerializePlaylist(GetRunDir() + MainDir + InfDir + "/" + id)
|
||||
|
||||
var upstream = func() {
|
||||
CreateImagelessPlaylistListBoxRow(PlayListListBox, item.Name, DurationToReadable(item.Duration), i, item.Entry)
|
||||
}
|
||||
glib.IdleAdd(upstream)
|
||||
}
|
||||
}
|
||||
|
||||
// func RefreshPlaylist() {
|
||||
// for child := PlayListListBox.FirstChild(); child != nil; {
|
||||
// PlayListListBox.Remove(child)
|
||||
// child = PlayListListBox.FirstChild()
|
||||
// }
|
||||
// param := map[string]string{}
|
||||
// playlists, err := CurrentClient.GetPlaylists(param)
|
||||
// LogOnError(err)
|
||||
// for i, item := range playlists {
|
||||
// playlist, _ := CurrentClient.GetPlaylist(item.ID)
|
||||
// CreateImagelessPlaylistListBoxRow(PlayListListBox, playlist.Name, DurationToReadable(playlist.Duration), i, playlist.Entry)
|
||||
// }
|
||||
// }
|
||||
|
||||
func DownloadLib() {
|
||||
param := map[string]string{}
|
||||
artistID3, err := CurrentClient.GetArtists(param)
|
||||
LogFatalOnError(err)
|
||||
|
||||
for _, indx := range artistID3.Index {
|
||||
for j, artist := range indx.Artist {
|
||||
art, _ := CurrentClient.GetArtist(artist.ID)
|
||||
ArtistsCached = append(ArtistsCached, artist.ID)
|
||||
|
||||
SerializeArtist(*art, GetRunDir()+MainDir+InfDir+"/"+artist.ID)
|
||||
|
||||
var upstream = func() {
|
||||
CreatelessListArtistBoxRow(QueueListBoxLibrary, art.Name, j, art.Album, art.ID)
|
||||
}
|
||||
glib.IdleAdd(upstream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetLib() {
|
||||
|
||||
for child := QueueListBoxLibrary.FirstChild(); child != nil; {
|
||||
QueueListBoxLibrary.Remove(child)
|
||||
child = QueueListBoxLibrary.FirstChild()
|
||||
}
|
||||
|
||||
for j, id := range ArtistsCached {
|
||||
art := DeSerializeArtist(GetRunDir() + MainDir + InfDir + "/" + id)
|
||||
var upstream = func() {
|
||||
CreatelessListArtistBoxRow(QueueListBoxLibrary, art.Name, j, art.Album, art.ID)
|
||||
}
|
||||
glib.IdleAdd(upstream)
|
||||
}
|
||||
}
|
||||
|
||||
// func RefreshLib() {
|
||||
// for child := QueueListBoxLibrary.FirstChild(); child != nil; {
|
||||
// QueueListBoxLibrary.Remove(child)
|
||||
// child = QueueListBoxLibrary.FirstChild()
|
||||
// }
|
||||
// param := map[string]string{}
|
||||
// artistID3, err := CurrentClient.GetArtists(param)
|
||||
// LogFatalOnError(err)
|
||||
|
||||
// for _, indx := range artistID3.Index {
|
||||
// for j, artist := range indx.Artist {
|
||||
// art, _ := CurrentClient.GetArtist(artist.ID)
|
||||
// CreatelessListArtistBoxRow(QueueListBoxLibrary, art.Name, j, art.Album)
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,39 @@
|
|||
# Contributor: ITmodulo <hello@xmpp.itmodulo.eu>
|
||||
# Maintainer: ITmodulo <hello@xmpp.itmodulo.eu>
|
||||
pkgname="sonically"
|
||||
reponame="Sonically"
|
||||
organization="LinuxOnMobile"
|
||||
githost="https://git.itmodulo.eu"
|
||||
pkgver=0.4.1
|
||||
pkgrel=0
|
||||
pkgdesc="Subsonic API compatible streaming client made in GTK. It is suitable for desktop and mobile"
|
||||
url="$githost/$organization/$reponame"
|
||||
arch="x86_64 aarch64"
|
||||
options="net !check" # for downloading Go modules, no checks available
|
||||
license="GPL-3.0-or-later"
|
||||
depends="portaudio opusfile libnotify glib gtk4.0 gobject-introspection"
|
||||
makedepends="go portaudio-dev opusfile-dev libnotify-dev glib-dev gtk4.0-dev gobject-introspection-dev"
|
||||
source="$reponame-$pkgver.tar.gz::$githost/$organization/$reponame/archive/$pkgver.tar.gz"
|
||||
builddir="$srcdir/$pkgname-$pkgver"
|
||||
|
||||
prepare() {
|
||||
default_prepare()
|
||||
mkdir -p "$builddir"
|
||||
}
|
||||
|
||||
build() {
|
||||
# GOARCH should be set automaticaly by OS
|
||||
cd "$srcdir"/"$pkgname"
|
||||
CGO_ENABLED="1" GOOS="linux" go build -tags netgo -o "$builddir"/"$pkgname"
|
||||
}
|
||||
|
||||
package() {
|
||||
# Place built executable in PATH
|
||||
install -Dm755 "$builddir"/"$pkgname" "$pkgdir"/usr/bin/"$pkgname"
|
||||
# Copy .svg logo to system
|
||||
install -Dm644 "$srcdir"/"$pkgname"/"$pkgname".svg "$pkgdir"/usr/share/pixmaps/"$pkgname".svg
|
||||
# Copy .desktop file to to system
|
||||
install -Dm755 "$srcdir"/"$pkgname"/"$pkgname".desktop "$pkgdir"/usr/share/applications/eu.itmodulo."$pkgname".desktop
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
// "image"
|
||||
"image/jpeg"
|
||||
// "bytes"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
// "github.com/esimov/stackblur-go"
|
||||
// "github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
// "github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
||||
// "github.com/diamondburned/gotk4/pkg/cairo"
|
||||
// "github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
// "github.com/diamondburned/gotkit/gtkutil/imgutil"
|
||||
)
|
||||
|
||||
func DownloadCoverArtFullSize(coverID string) {
|
||||
|
||||
path := GetRunDir() + MainDir + DataDir + "/" + coverID + ".jpg"
|
||||
|
||||
fil, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664)
|
||||
defer fil.Close()
|
||||
if err1 != nil {
|
||||
//
|
||||
param := map[string]string{"size": strconv.Itoa(int(MainSettings.Coversize))}
|
||||
|
||||
img, _ := CurrentClient.GetCoverArt(coverID, param)
|
||||
fmt.Println("No such album downloaded: ", coverID)
|
||||
f, err := os.Create(path)
|
||||
// fb, err := os.Create(path2)
|
||||
PanicOnError(err)
|
||||
defer f.Close()
|
||||
if err = jpeg.Encode(f, img, nil); err != nil {
|
||||
log.Printf("failed to encode: %v", err)
|
||||
|
||||
}
|
||||
|
||||
// w, h := MainAlbumCover.GetSizeRequest()
|
||||
|
||||
// catFile, err := os.Open(path)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// defer catFile.Close()
|
||||
// _, _, err = image.Decode(catFile)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// cat, err := jpeg.Decode(catFile)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// fmt.Println(cat)
|
||||
// sig := make(chan struct{})
|
||||
// img2 := stackblur.Process(cat, uint32(w), uint32(h), uint32(30), sig)
|
||||
// select {
|
||||
|
||||
// case <-sig:
|
||||
// jpeg.Encode(fb, img2, nil)
|
||||
// }
|
||||
} else {
|
||||
|
||||
fmt.Println("Album present: ", coverID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// func LoadImageToByteArray(path string) []byte {
|
||||
|
||||
// f, err := os.Open(path)
|
||||
// if err != nil {
|
||||
// LogOnError(err)
|
||||
// }
|
||||
// defer f.Close()
|
||||
// buf := new(bytes.Buffer)
|
||||
// img, err := jpeg.Decode(f)
|
||||
// err = jpeg.Encode(buf,img,nil)
|
||||
|
||||
// if err != nil {
|
||||
// LogOnError(err)
|
||||
// }
|
||||
|
||||
// return buf.Bytes()
|
||||
// }
|
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// "log"
|
||||
"strconv"
|
||||
// "fmt"
|
||||
// "github.com/diamondburned/gotk4/pkg/core/glib"
|
||||
// "github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
// "github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
// "github.com/diamondburned/gotkit/gtkutil"
|
||||
)
|
||||
|
||||
// var PlayNode string
|
||||
// var pool *parallel = parallel.NewParallel()
|
||||
|
||||
func Search() {
|
||||
|
||||
for child := SearchListBoxResult.FirstChild(); child != nil; {
|
||||
SearchListBoxResult.Remove(child)
|
||||
child = SearchListBoxResult.FirstChild()
|
||||
}
|
||||
|
||||
artistCount := 0
|
||||
albumCount := 0
|
||||
songCount := 0
|
||||
|
||||
if SearchTabSongCheckButton.Active() {
|
||||
songCount = 20
|
||||
}
|
||||
if SearchTabAlbumCheckButton.Active() {
|
||||
albumCount = 20
|
||||
}
|
||||
if SearchTabArtistCheckButton.Active() {
|
||||
artistCount = 20
|
||||
}
|
||||
q := SearchTabSearchEntry.Text()
|
||||
param := map[string]string{
|
||||
"artistCount": strconv.Itoa(artistCount),
|
||||
// "artistOffset": "0",
|
||||
"albumCount": strconv.Itoa(albumCount),
|
||||
// "albumOffset": "0",
|
||||
"songCount": strconv.Itoa(songCount),
|
||||
// "songOffset": "0",
|
||||
// "musicFolderId": "",
|
||||
}
|
||||
if q != "" {
|
||||
result, err := CurrentClient.Search3(q, param)
|
||||
LogFatalOnError(err)
|
||||
var j = 0
|
||||
for _, item := range result.Song {
|
||||
j++
|
||||
CreateImagelessSongListBoxRow(SearchListBoxResult, item.Title, item.Artist, DurationToReadable(item.Duration), j, item.ID)
|
||||
}
|
||||
for _, item := range result.Album {
|
||||
j++
|
||||
album, _ := CurrentClient.GetAlbum(item.ID)
|
||||
CreateImagelessAlbumListBoxRow(SearchListBoxResult, album.Name, album.Artist, DurationToReadable(album.Duration), j, album.Song)
|
||||
}
|
||||
for _, item := range result.Artist {
|
||||
j++
|
||||
artist, _ := CurrentClient.GetArtist(item.ID)
|
||||
CreatelessListArtistBoxRow(SearchListBoxResult, artist.Name, j, artist.Album, item.ID)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/delucks/go-subsonic"
|
||||
"github.com/diamondburned/gotk4/pkg/core/glib"
|
||||
// "io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
// "github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"github.com/djherbis/fscache"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
func TestConnButtonClicked() {
|
||||
clientt := CreateUser(HttpSettings)
|
||||
_, err := clientt.GetGenres()
|
||||
if err != nil {
|
||||
var up = func() bool {
|
||||
SettingsTabUserStatusLabel.SetText("✗ ")
|
||||
return false
|
||||
}
|
||||
glib.IdleAdd(up)
|
||||
|
||||
} else {
|
||||
var up = func() bool {
|
||||
SettingsTabUserStatusLabel.SetText("✓ ")
|
||||
return false
|
||||
}
|
||||
glib.IdleAdd(up)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUser(httpsettings *http.Client) subsonic.Client {
|
||||
server := SettingsTabUserServerEntry.Text()
|
||||
username := SettingsTabUserUsernameEntry.Text()
|
||||
device := SettingsTabUserDeviceNameEntry.Text()
|
||||
pass := SettingsTabUserPasswordEntry.Text()
|
||||
|
||||
clientt := subsonic.Client{
|
||||
Client: httpsettings,
|
||||
BaseUrl: server,
|
||||
User: username,
|
||||
ClientName: device,
|
||||
}
|
||||
clientt.Authenticate(pass)
|
||||
|
||||
return clientt
|
||||
}
|
||||
|
||||
type User struct {
|
||||
User string
|
||||
Url string
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
//User
|
||||
Users [10]User
|
||||
PlayerName string
|
||||
|
||||
//Local
|
||||
DeleteOnClose bool
|
||||
Coversize int16
|
||||
BlurRadius int8
|
||||
HideHeaderBar bool
|
||||
|
||||
//Transcoding
|
||||
Codec string
|
||||
Bitrate int16
|
||||
DownladBufferSize int16
|
||||
|
||||
//Net
|
||||
UseStreamingProxy bool
|
||||
StreamingProxy string
|
||||
AuthStreamingProxy bool
|
||||
StreamingProxyUser string
|
||||
|
||||
// Metadata
|
||||
MetadataType string
|
||||
UseMetadataProxy bool
|
||||
MetadataProxy string
|
||||
AuthMetadataProxy bool
|
||||
MetadataProxyUser string
|
||||
}
|
||||
|
||||
// type SonicallyStream struct {
|
||||
// ReadMaster fscache.ReadAtCloser
|
||||
// WriteMaster io.WriteCloser
|
||||
// }
|
||||
|
||||
func Serialize(obj Settings, path string) {
|
||||
dataFile, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664)
|
||||
defer dataFile.Close()
|
||||
if err1 == nil {
|
||||
os.Remove(path)
|
||||
}
|
||||
dataFile, _ = os.Create(path)
|
||||
e := gob.NewEncoder(dataFile)
|
||||
if err := e.Encode(obj); err != nil {
|
||||
LogFatalOnError(err)
|
||||
}
|
||||
dataFile.Close()
|
||||
}
|
||||
|
||||
func DeSerialize(path string) Settings {
|
||||
dataFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
var sett Settings
|
||||
dataDecoder := gob.NewDecoder(dataFile)
|
||||
err = dataDecoder.Decode(&sett)
|
||||
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
dataFile.Close()
|
||||
return sett
|
||||
}
|
||||
|
||||
func SerializeArtist(obj subsonic.ArtistID3, path string) {
|
||||
dataFile, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664)
|
||||
defer dataFile.Close()
|
||||
if err1 == nil {
|
||||
os.Remove(path)
|
||||
}
|
||||
dataFile, _ = os.Create(path)
|
||||
e := gob.NewEncoder(dataFile)
|
||||
if err := e.Encode(obj); err != nil {
|
||||
LogFatalOnError(err)
|
||||
}
|
||||
dataFile.Close()
|
||||
}
|
||||
|
||||
func DeSerializeArtist(path string) subsonic.ArtistID3 {
|
||||
dataFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
var sett subsonic.ArtistID3
|
||||
dataDecoder := gob.NewDecoder(dataFile)
|
||||
err = dataDecoder.Decode(&sett)
|
||||
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
dataFile.Close()
|
||||
return sett
|
||||
}
|
||||
|
||||
func SerializePlaylist(obj subsonic.Playlist, path string) {
|
||||
dataFile, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664)
|
||||
defer dataFile.Close()
|
||||
if err1 == nil {
|
||||
os.Remove(path)
|
||||
}
|
||||
dataFile, _ = os.Create(path)
|
||||
e := gob.NewEncoder(dataFile)
|
||||
if err := e.Encode(obj); err != nil {
|
||||
LogFatalOnError(err)
|
||||
}
|
||||
dataFile.Close()
|
||||
}
|
||||
|
||||
func DeSerializePlaylist(path string) subsonic.Playlist {
|
||||
dataFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
var sett subsonic.Playlist
|
||||
dataDecoder := gob.NewDecoder(dataFile)
|
||||
err = dataDecoder.Decode(&sett)
|
||||
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
dataFile.Close()
|
||||
return sett
|
||||
}
|
||||
|
||||
func Init() {
|
||||
fil, err1 := os.OpenFile(settingsPath, os.O_APPEND|os.O_WRONLY, 0664)
|
||||
defer fil.Close()
|
||||
if err1 == nil {
|
||||
MainSettings = DeSerialize(settingsPath)
|
||||
UserList = MainSettings.Users
|
||||
UserDeserialized = MainSettings.Users[0]
|
||||
|
||||
clientt := subsonic.Client{
|
||||
Client: HttpSettings,
|
||||
BaseUrl: UserDeserialized.Url,
|
||||
User: UserDeserialized.User,
|
||||
ClientName: MainSettings.PlayerName,
|
||||
}
|
||||
|
||||
sec, err := keyring.Get(UserDeserialized.Url, UserDeserialized.User)
|
||||
if err == nil {
|
||||
clientt.Authenticate(sec)
|
||||
CurrentClient = clientt
|
||||
SettingsTabUserUsernameEntry.SetText(UserDeserialized.User)
|
||||
SettingsTabUserPasswordEntry.SetText(sec)
|
||||
SettingsTabUserDeviceNameEntry.SetText(MainSettings.PlayerName)
|
||||
SettingsTabUserServerEntry.SetText(UserDeserialized.Url)
|
||||
SettingsTabAppDeleteSwitch.SetActive(MainSettings.DeleteOnClose)
|
||||
SettingsTabAppHideHeaderBarSwitch.SetActive(MainSettings.HideHeaderBar)
|
||||
|
||||
SettingsTabAppCoverSizeEntry.SetText(strconv.Itoa(int(MainSettings.Coversize)))
|
||||
SettingsTabAppBlurRadiusEntry.SetText(strconv.Itoa(int(MainSettings.BlurRadius)))
|
||||
SettingsTabAppBitrateEntry.SetText(strconv.Itoa(int(MainSettings.Bitrate)))
|
||||
SettingsTabAppBufferSizeEntry.SetText(strconv.Itoa(int(MainSettings.DownladBufferSize)))
|
||||
SettingsTabAppStreamingProxySwitch.SetActive(MainSettings.UseStreamingProxy)
|
||||
if MainSettings.UseStreamingProxy == true {
|
||||
SettingsTabAppStreamingProxyEntry.SetText(MainSettings.StreamingProxy)
|
||||
|
||||
// var temptrans *http.Transport
|
||||
var protocol, parsed string
|
||||
|
||||
if strings.Contains(MainSettings.StreamingProxy, "https") {
|
||||
protocol = "https://"
|
||||
parsed = MainSettings.StreamingProxy[8:]
|
||||
} else if strings.Contains(MainSettings.StreamingProxy, "socks5") {
|
||||
protocol = "socks5://"
|
||||
parsed = MainSettings.StreamingProxy[9:]
|
||||
} else if strings.Contains(MainSettings.StreamingProxy, "http") && !strings.Contains(MainSettings.StreamingProxy, "https") {
|
||||
protocol = "http://"
|
||||
parsed = MainSettings.StreamingProxy[7:]
|
||||
} else {
|
||||
log.Fatal("Unsupported protocol")
|
||||
}
|
||||
|
||||
if SettingsTabAppAuthStreamingProxySwitch.Active() == true {
|
||||
sec2, err := keyring.Get(MainSettings.StreamingProxy, MainSettings.StreamingProxyUser)
|
||||
if err != nil {
|
||||
log.Fatal("Error getting from keyring")
|
||||
}
|
||||
SettingsTabAppStreamingProxyUsernameEntry.SetText(MainSettings.StreamingProxyUser)
|
||||
SettingsTabAppStreamingProxyPassEntry.SetText(sec2)
|
||||
|
||||
fullURL := protocol + MainSettings.StreamingProxyUser + ":" + sec2 + "@" + parsed
|
||||
//adding the proxy settings to the Transport object
|
||||
parsedurl, _ := url.Parse(fullURL)
|
||||
|
||||
HttpSettings = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl)}}
|
||||
|
||||
} else {
|
||||
// fullURL := protocol + MainSettings.StreamingProxyUser + "@" + parsed
|
||||
parsedurl, _ := url.Parse(MainSettings.StreamingProxy)
|
||||
// temptrans :=
|
||||
HttpSettings = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl)}}
|
||||
}
|
||||
|
||||
//adding the Transport object to the http Client
|
||||
|
||||
// HttpSettings.Transport
|
||||
|
||||
}
|
||||
|
||||
SettingsTabAppMetadataProxySwitch.SetActive(MainSettings.UseMetadataProxy)
|
||||
if MainSettings.UseMetadataProxy == true {
|
||||
SettingsTabAppMetadataProxyEntry.SetText(MainSettings.MetadataProxy)
|
||||
|
||||
// var temptrans2 *http.Transport
|
||||
var protocol, parsed string
|
||||
|
||||
if strings.Contains(MainSettings.MetadataProxy, "https") {
|
||||
protocol = "https://"
|
||||
parsed = MainSettings.MetadataProxy[8:]
|
||||
} else if strings.Contains(MainSettings.MetadataProxy, "socks5") {
|
||||
protocol = "socks5://"
|
||||
parsed = MainSettings.MetadataProxy[9:]
|
||||
} else if strings.Contains(MainSettings.MetadataProxy, "http") && !strings.Contains(MainSettings.MetadataProxy, "https") {
|
||||
protocol = "http://"
|
||||
parsed = MainSettings.MetadataProxy[7:]
|
||||
} else {
|
||||
log.Fatal("Unsupported protocol")
|
||||
}
|
||||
|
||||
if SettingsTabAppAuthMetadataProxySwitch.Active() == true {
|
||||
sec3, err := keyring.Get(MainSettings.MetadataProxy, MainSettings.MetadataProxyUser)
|
||||
if err != nil {
|
||||
log.Fatal("Error getting from keyring")
|
||||
}
|
||||
SettingsTabAppMetadataProxyUsernameEntry.SetText(MainSettings.MetadataProxyUser)
|
||||
SettingsTabAppMetadataProxyPassEntry.SetText(sec3)
|
||||
|
||||
fullURL := protocol + MainSettings.MetadataProxyUser + ":" + sec3 + "@" + parsed
|
||||
//adding the proxy settings to the Transport object
|
||||
parsedurl2, err := url.Parse(fullURL)
|
||||
MetadataClient = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl2)}}
|
||||
|
||||
} else {
|
||||
// fullURL := protocol + MainSettings.StreamingProxyUser + "@" + parsed
|
||||
parsedurl2, _ := url.Parse(MainSettings.MetadataProxy)
|
||||
MetadataClient = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl2)}}
|
||||
|
||||
}
|
||||
|
||||
//adding the Transport object to the http Client
|
||||
|
||||
}
|
||||
if MainSettings.Codec == "opus" {
|
||||
SettingsTabAppCodecComboBoxTest.SetActive(0)
|
||||
} else if MainSettings.Codec == "flac" {
|
||||
SettingsTabAppCodecComboBoxTest.SetActive(1)
|
||||
} else if MainSettings.Codec == "ogg" {
|
||||
SettingsTabAppCodecComboBoxTest.SetActive(2)
|
||||
} else if MainSettings.Codec == "mp3" {
|
||||
SettingsTabAppCodecComboBoxTest.SetActive(3)
|
||||
}
|
||||
|
||||
// HERE
|
||||
|
||||
if MainSettings.MetadataType == "None" {
|
||||
SettingsTabAppMetadataComboBoxTest.SetActive(0)
|
||||
} else if MainSettings.MetadataType == "Server" {
|
||||
SettingsTabAppMetadataComboBoxTest.SetActive(1)
|
||||
} else if MainSettings.MetadataType == "Sonically" {
|
||||
SettingsTabAppMetadataComboBoxTest.SetActive(2)
|
||||
}
|
||||
|
||||
if MainSettings.HideHeaderBar {
|
||||
MainWindow.SetDecorated(false)
|
||||
}
|
||||
|
||||
log.Print("Sucessfully set user from deserialzed content")
|
||||
cacheDir := GetRunDir() + MainDir + DataDir + "/.cache"
|
||||
CurrentCache, err = fscache.New(cacheDir, 0755, time.Hour*24)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MainDirC := GetRunDir() + MainDir
|
||||
if _, err := os.Stat(MainDir); os.IsNotExist(err) {
|
||||
os.Mkdir(MainDirC, 0744)
|
||||
os.Mkdir(MainDirC+DataDir, 0744)
|
||||
os.Mkdir(MainDirC+InfDir, 0744)
|
||||
os.Mkdir(MainDirC+SettingsDir, 0744)
|
||||
fmt.Println("Directory tree created")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/TheCreeper/go-notify"
|
||||
"github.com/delucks/go-subsonic"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
// "github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
// "github.com/diamondburned/gotk4/pkg/core/glib"
|
||||
"github.com/djherbis/fscache"
|
||||
// "github.com/emirpasic/gods/lists/arraylist"
|
||||
"github.com/gordonklaus/portaudio"
|
||||
"github.com/hraban/opus"
|
||||
)
|
||||
|
||||
func GetRunDir() string {
|
||||
rundirGet, _ := os.UserHomeDir()
|
||||
return rundirGet
|
||||
}
|
||||
|
||||
// Dirs
|
||||
const MainDir string = "/.Sonically"
|
||||
const DataDir string = "/data"
|
||||
const SettingsDir string = "/Settings"
|
||||
const InfDir string = "/inf"
|
||||
|
||||
var CurrentClient subsonic.Client
|
||||
var CurrentStream *portaudio.Stream
|
||||
var MainQueue []string
|
||||
var NowPlayingIndex int = 0
|
||||
var UserList [10]User
|
||||
var UserDeserialized User
|
||||
var MainSettings Settings
|
||||
var DownloadSyncGroup sync.WaitGroup
|
||||
var TotalTime int = 0
|
||||
var DrawnTime float64 = float64(0.0)
|
||||
var Out []int16 = make([]int16, 1920)
|
||||
|
||||
// var ReadBuffer []byte
|
||||
// var MasterStramSlice map[string]SonicallyStream
|
||||
var CachingMap map[string]bool = make(map[string]bool)
|
||||
|
||||
var TimeInPrecent float64 = 0.0
|
||||
var CurrentCache fscache.Cache
|
||||
|
||||
var AudioControlChan chan bool
|
||||
var OpusStr *opus.Stream
|
||||
var ReadMaster fscache.ReadAtCloser
|
||||
var WriteMaster io.WriteCloser
|
||||
|
||||
var ArtistsCached []string
|
||||
var PlaylistCached []string
|
||||
var TempArtist string = ""
|
||||
var EnterArtist bool
|
||||
|
||||
// UI widgets declaration
|
||||
var MainWindow *gtk.ApplicationWindow
|
||||
var LyricsNotebook *gtk.Notebook
|
||||
var LyricsScrolledWindow *gtk.ScrolledWindow
|
||||
var MainFlowbox *gtk.FlowBox
|
||||
var MainFlowBoxChild1 *gtk.FlowBoxChild
|
||||
var MainFlowBoxChild2 *gtk.FlowBoxChild
|
||||
var MainFlowBoxChild3 *gtk.FlowBoxChild
|
||||
var MainFlowBoxChild4 *gtk.FlowBoxChild
|
||||
var MainGridFlowBoxChild2 *gtk.Grid
|
||||
var MainGridFlowBoxChild4 *gtk.Grid
|
||||
var MainGridFlowBoxChild2Grid *gtk.Grid
|
||||
var MainLeftSeparator *gtk.Separator
|
||||
var MainRightSeparator *gtk.Separator
|
||||
var MainScale *gtk.Scale
|
||||
var MainScaleGrid *gtk.Grid
|
||||
|
||||
var QueueMasterBox *gtk.Box
|
||||
var QueueSearchControlBox *gtk.Grid
|
||||
var QueueFlowBox *gtk.FlowBox
|
||||
var QueueScrolledWindow *gtk.ScrolledWindow
|
||||
var QueueSaveButton *gtk.Button
|
||||
var QueueClearButton *gtk.Button
|
||||
var QueueShuffleButton *gtk.Button
|
||||
var QueueFlowBoxChild1 *gtk.FlowBoxChild
|
||||
|
||||
var SearchFlowBox *gtk.FlowBox
|
||||
var SearchFlowBoxChild1 *gtk.FlowBoxChild
|
||||
var SearchMasterBox *gtk.Box
|
||||
var SearchControlBox *gtk.Grid
|
||||
var SearchScrolledWindow *gtk.ScrolledWindow
|
||||
|
||||
var LibraryFlowBox1 *gtk.FlowBox
|
||||
var LibraryFlowBox2 *gtk.FlowBox
|
||||
var LibraryFlowBox3 *gtk.FlowBox
|
||||
var LibraryFlowBox4 *gtk.FlowBox
|
||||
|
||||
var LibraryPlaylistMainBox *gtk.Box
|
||||
var LibraryStarredMainBox *gtk.Box
|
||||
var LibraryLibraryMainBox *gtk.Box
|
||||
var LibraryPodcastMainBox *gtk.Box
|
||||
var LibraryFlowBox1Child1 *gtk.FlowBoxChild
|
||||
var LibraryFlowBox2Child1 *gtk.FlowBoxChild
|
||||
var LibraryFlowBox3Child1 *gtk.FlowBoxChild
|
||||
var LibraryFlowBox4Child1 *gtk.FlowBoxChild
|
||||
var LibraryPlaylistMainControlGrid *gtk.Grid
|
||||
var LibraryStarredMainControlGrid *gtk.Grid
|
||||
var LibraryLibraryMainControlGrid *gtk.Grid
|
||||
var LibraryPodcastMainControlGrid *gtk.Grid
|
||||
var LibraryPlaylistBackButton *gtk.Button
|
||||
var LibraryPlaylistRefreshButton *gtk.Button
|
||||
var LibraryStarredBackButton *gtk.Button
|
||||
var LibraryStarredRefreshButton *gtk.Button
|
||||
var LibraryLibraryBackButton *gtk.Button
|
||||
var LibraryLibraryRefreshButton *gtk.Button
|
||||
var LibraryPodcastBackButton *gtk.Button
|
||||
var LibraryPodcastRefreshButton *gtk.Button
|
||||
var LibraryStarredListBox *gtk.ListBox
|
||||
var LibraryPodcastListBox *gtk.ListBox
|
||||
var LibraryPlaylistMainScrolledWindow *gtk.ScrolledWindow
|
||||
var LibraryPodcastListBoxScrolledWindow *gtk.ScrolledWindow
|
||||
var LibraryStarredListBoxScrolledWindow *gtk.ScrolledWindow
|
||||
var QueueListBoxLibraryScrolledWindow *gtk.ScrolledWindow
|
||||
|
||||
var SettingsFlowBox *gtk.FlowBox
|
||||
var SettingsTabUserUsernameEntry *gtk.Entry
|
||||
var SettingsTabUserServerEntry *gtk.Entry
|
||||
var SettingsTabUserDeviceNameEntry *gtk.Entry
|
||||
var SettingsTabUserPasswordEntry *gtk.Entry
|
||||
var SettingsTabUserStatusLabel *gtk.Label
|
||||
var SearchTabSearchEntry *gtk.SearchEntry
|
||||
var SearchTabSongCheckButton *gtk.CheckButton
|
||||
var SearchTabAlbumCheckButton *gtk.CheckButton
|
||||
var SearchTabArtistCheckButton *gtk.CheckButton
|
||||
var SearchListBoxResult *gtk.ListBox
|
||||
var PlayListListBox *gtk.ListBox
|
||||
var MainSongTitleLabel *gtk.Label
|
||||
var MainArtistLabel *gtk.Label
|
||||
var MainAlbumLabel *gtk.Label
|
||||
var MainTimeDrawnLabel *gtk.Label
|
||||
var MainTimeLeft *gtk.Label
|
||||
var MainAdjustment *gtk.Adjustment
|
||||
var SettingsTabAppDeleteSwitch *gtk.Switch
|
||||
var SettingsTabAppCodecComboBoxTest *gtk.ComboBoxText
|
||||
|
||||
// var SettingsTabAppRetrievingComboBoxText *gtk.ComboBoxText
|
||||
var SettingsTabAppCacheEntry *gtk.Entry
|
||||
var QueueListBox *gtk.ListBox
|
||||
var MainAlbumCover *gtk.Image
|
||||
var MainPlayPauseButton *gtk.Button
|
||||
var MainPreviousButton *gtk.Button
|
||||
var MainNextButton *gtk.Button
|
||||
var MainRepeatButton *gtk.Button
|
||||
var MainStarredButton *gtk.Button
|
||||
var MainSubtitlesTextView *gtk.TextView
|
||||
var MainTotalTimeLabel *gtk.Label
|
||||
var LibraryNotebook *gtk.Notebook
|
||||
var MainNotebook *gtk.Notebook
|
||||
var QueueListBoxLibrary *gtk.ListBox
|
||||
var SettingsTabAppCoverSizeEntry *gtk.Entry
|
||||
var SettingsTabAppBlurRadiusEntry *gtk.Entry
|
||||
var SettingsTabAppBitrateEntry *gtk.Entry
|
||||
var SettingsTabAppBufferSizeEntry *gtk.Entry
|
||||
var SettingsTabAppStreamingProxySwitch *gtk.Switch
|
||||
var SettingsTabAppStreamingProxyEntry *gtk.Entry
|
||||
var SettingsTabAppAuthStreamingProxySwitch *gtk.Switch
|
||||
var SettingsTabAppStreamingProxyUsernameEntry *gtk.Entry
|
||||
var SettingsTabAppStreamingProxyPassEntry *gtk.Entry
|
||||
|
||||
var SettingsTabAppMetadataProxySwitch *gtk.Switch
|
||||
var SettingsTabAppMetadataProxyEntry *gtk.Entry
|
||||
var SettingsTabAppAuthMetadataProxySwitch *gtk.Switch
|
||||
var SettingsTabAppMetadataProxyUsernameEntry *gtk.Entry
|
||||
var SettingsTabAppMetadataProxyPassEntry *gtk.Entry
|
||||
|
||||
var SettingsAccountsFlowBox *gtk.FlowBox
|
||||
var SettingsAccountFlowboxChild1 *gtk.FlowBoxChild
|
||||
var SettingsAccountFlowboxChild2 *gtk.FlowBoxChild
|
||||
|
||||
var SettingsNotebook *gtk.Notebook
|
||||
var SettingsAccountBox *gtk.Box
|
||||
var SettingsAccountLoginGrid *gtk.Grid
|
||||
var SettingsAccountButtonsGrid *gtk.Grid
|
||||
var SettingsAccountUserListbox *gtk.ListBox
|
||||
var SettingsAppFlowBox *gtk.FlowBox
|
||||
var SettingsCreditsFlowBox *gtk.FlowBox
|
||||
|
||||
var SettingsAccountUsernameLabel *gtk.Label
|
||||
var SettingsAccountPasswordLabel *gtk.Label
|
||||
var SettingsAccountPLayerNameLabel *gtk.Label
|
||||
var SettingsAccountServerLabel *gtk.Label
|
||||
|
||||
var SettingsAccountTestButton *gtk.Button
|
||||
var SettingsAccountSaveButton *gtk.Button
|
||||
var SettingsAccountLoadButton *gtk.Button
|
||||
|
||||
// var SettingsAppScrolledWindow *gtk.ScrolledWindow
|
||||
var SettingsAppFlowBoxChild1 *gtk.FlowBoxChild
|
||||
var SettingsAppFlowBoxChild2 *gtk.FlowBoxChild
|
||||
var SettingsAppFlowBoxChild3 *gtk.FlowBoxChild
|
||||
var SettingsAppFlowBoxChild4 *gtk.FlowBoxChild
|
||||
var SettingsAppGrid1 *gtk.Grid
|
||||
var SettingsAppGrid2 *gtk.Grid
|
||||
var SettingsAppGrid3 *gtk.Grid
|
||||
var SettingsAppGrid4 *gtk.Grid
|
||||
|
||||
var SettingsAppLabelGrid4 *gtk.Label
|
||||
var SettingsAppLabelGrid4Metadata *gtk.Label
|
||||
var SettingsTabAppMetadataComboBoxTest *gtk.ComboBoxText
|
||||
|
||||
var SettingsAppLabelGrid1 *gtk.Label
|
||||
var SettingsAppLabelGrid2 *gtk.Label
|
||||
var SettingsAppLabelGrid3 *gtk.Label
|
||||
|
||||
var SettingsAppLabelGrid1DeleteOnClose *gtk.Label
|
||||
var SettingsAppLabelGrid1CoverSideSize *gtk.Label
|
||||
var SettingsAppLabelGrid1BackgroundBlurRadius *gtk.Label
|
||||
|
||||
var SettingsAppLabelGrid2Codec *gtk.Label
|
||||
var SettingsAppLabelGrid2Bitrate *gtk.Label
|
||||
var SettingsAppLabelGrid2DownloadBufferSize *gtk.Label
|
||||
|
||||
var SettingsTabAppHideHeaderBarSwitch *gtk.Switch
|
||||
var SettingsAppLabelHideHeaderBar *gtk.Label
|
||||
|
||||
var SettingsAccountScrolledWindow *gtk.ScrolledWindow
|
||||
var SettingsAppScrolledWindow *gtk.ScrolledWindow
|
||||
var SettingsCreditsScrolledWindow *gtk.ScrolledWindow
|
||||
|
||||
var SettingsAppLabelGrid3UseProxyForStreaming *gtk.Label
|
||||
var SettingsAppLabelGrid3AuthenticateStreamingProxy *gtk.Label
|
||||
var SettingsAppLabelGrid3UseProxyForMetadata *gtk.Label
|
||||
var SettingsAppLabelGrid3AuthenticateMetadataProxy *gtk.Label
|
||||
var SettingsCreditsFlowBoxChild1 *gtk.FlowBoxChild
|
||||
var SettingsCreditsLabel1 *gtk.Label
|
||||
|
||||
var MetadataClient *http.Client
|
||||
var HttpSettings *http.Client
|
||||
|
||||
// var one gtk.EventBox
|
||||
|
||||
// LIBNOTIFY
|
||||
|
||||
var MainNotification notify.Notification
|
|
@ -0,0 +1,59 @@
|
|||
package main
|
||||
|
||||
// Slice Utils instead of ArrayList
|
||||
|
||||
// Returns Index of T
|
||||
func SliceIndexOf[T comparable](slc []T, el T) int {
|
||||
for i, x := range slc {
|
||||
if x == el {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Swap T1 and T2 at indexes
|
||||
func SliceSwapAtIndexes[T comparable](slc []T, el int, el2 int) {
|
||||
x := slc[el]
|
||||
slc[el] = slc[el2]
|
||||
slc[el2] = x
|
||||
}
|
||||
|
||||
// Returns true if Slice contains T
|
||||
func SliceContains[T comparable](slc []T, el T) bool {
|
||||
for _, x := range slc {
|
||||
if x == el {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove T if exists
|
||||
func SliceRemove[T comparable](slc []T, el T) []T {
|
||||
res := SliceIndexOf(slc, el)
|
||||
if res != -1 {
|
||||
return SliceRemoveAt(slc, res)
|
||||
}
|
||||
return slc
|
||||
}
|
||||
|
||||
func SliceRemoveAt[T comparable](slc []T, el int) []T {
|
||||
for i := el; i <= len(slc)-2; i++ {
|
||||
slc[i] = slc[i+1]
|
||||
}
|
||||
// x:= (len(slc)-2)
|
||||
// slc = append([]T(nil), slc[:x]...)
|
||||
return slc[:(len(slc) - 1)]
|
||||
}
|
||||
|
||||
func SliceInsertAt[T comparable](slc []T, el T, pos int) {
|
||||
slc = append(slc, slc[len(slc)-1])
|
||||
for i := len(slc) - 2; i > pos; i-- {
|
||||
slc[i] = slc[i-1]
|
||||
}
|
||||
}
|
||||
|
||||
func SliceClear[T comparable](slc []T) []T {
|
||||
return []T{}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Version=0.4.1
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Exec=/usr/bin/sonically
|
||||
Name=Sonically
|
||||
Icon=/usr/share/pixmaps/sonically.svg
|
|
@ -0,0 +1,110 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// "net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/TheCreeper/go-notify"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
// "github.com/diamondburned/gotk4/pkg/core/glib"
|
||||
// "github.com/hashicorp/go-retryablehttp"
|
||||
)
|
||||
|
||||
// Widely used, conditionally created objects
|
||||
var ifDelete bool = true
|
||||
|
||||
var delDir string
|
||||
var settingsPath string = (GetRunDir() + MainDir + SettingsDir + "/gob")
|
||||
|
||||
// var mainWaitGroup sync.WaitGroup
|
||||
|
||||
// This is where app starts
|
||||
func main() {
|
||||
app := gtk.NewApplication("eu.itmodulo.sonically", 0)
|
||||
app.ConnectActivate(func() { activate(app) })
|
||||
|
||||
if code := app.Run(os.Args); code > 0 {
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
|
||||
// This is func that initates GUI, deserializez data
|
||||
func activate(app *gtk.Application) {
|
||||
AudioControlChan = make(chan bool) // Initate goroutine channel
|
||||
MainWindow = gtk.NewApplicationWindow(app)
|
||||
MainWindow.SetTitle("Sonically")
|
||||
SetUpGui()
|
||||
|
||||
x := retryablehttp.NewClient()
|
||||
x.RetryMax = 15
|
||||
x.Logger = nil
|
||||
|
||||
y := retryablehttp.NewClient()
|
||||
y.RetryMax = 20
|
||||
y.Logger = nil
|
||||
// All widgets put together
|
||||
HttpSettings = x.StandardClient() // Default client for connection
|
||||
MetadataClient = y.StandardClient() // Default client for connection
|
||||
Init() // Set up account
|
||||
MainWindow.ConnectCloseRequest(closeApp)
|
||||
MainWindow.Resizable()
|
||||
|
||||
MainWindow.Show()
|
||||
|
||||
// Background Read User Library, to speedup later loading
|
||||
if MainSettings.Bitrate != 0 {
|
||||
go DownloadLib()
|
||||
go DownloadPlaylist()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func closeApp() bool {
|
||||
if CurrentCache != nil {
|
||||
CurrentCache.Clean()
|
||||
}
|
||||
if MainSettings.DeleteOnClose {
|
||||
delDir = GetRunDir() + MainDir + DataDir + "/"
|
||||
|
||||
d, err := os.Open(delDir)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
defer d.Close()
|
||||
names, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
for _, name := range names {
|
||||
err = os.RemoveAll(filepath.Join(delDir, name))
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
delDir = GetRunDir() + MainDir + InfDir + "/"
|
||||
|
||||
d, err := os.Open(delDir)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
defer d.Close()
|
||||
names, err := d.Readdirnames(-1)
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
for _, name := range names {
|
||||
err = os.RemoveAll(filepath.Join(delDir, name))
|
||||
if err != nil {
|
||||
PanicOnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
notify.CloseNotification(MainNotification.ReplacesID)
|
||||
runtime.GC()
|
||||
return false
|
||||
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in New Issue