Initial commit

This commit is contained in:
Ilya Suhodolskiy
2023-05-13 19:20:49 +04:00
commit 7afbff6a12
10 changed files with 235 additions and 0 deletions

BIN
.github/cover.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.DS_Store
__debug_bin
dist

14
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Run App",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"cwd": "${workspaceFolder}",
"args": ["-port", "5173"]
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"cSpell.words": ["ctrack", "osascript", "tmpl"]
}

2
Makefile Normal file
View File

@@ -0,0 +1,2 @@
build_app:
go build -ldflags="-s -w" -o ./dist/obs-spotify ./main.go

16
README.md Normal file
View File

@@ -0,0 +1,16 @@
# OBS Spotify
![Cover](https://github.com/suhodolskiy/obs-spotify/blob/main/.github/cover.jpg)
## How to use?
1. Download ZIP from last release
2. Open obs-spotify binary
3. Add new "Browser" source in OBS
4. Add http://localhost:3000 url
5. Save source
### Options
1. `obs-spotify` accepts the port argument. Example ./obs-spotify -port=8888
2. `?refresh=5s`

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module spotify-obs
go 1.18

0
go.sum Normal file
View File

75
index.html Normal file
View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spotify</title>
<style>
body {
display: flex;
}
.spotify {
display: flex;
align-items: center;
column-gap: 16px;
/* Font */
font-family: Inter;
font-size: 24px;
color: #fff;
}
.image {
width: 52px;
height: 52px;
border-radius: 8px;
background-color: #e9e9e9;
overflow: hidden;
}
#cover {
width: 100%;
height: 100%;
object-fit: cover;
}
#artist {
opacity: 0.6;
font-size: 16px;
}
</style>
</head>
<body>
<div class="spotify">
<div class="image">
<img id="cover" src="{{ .Track.ArtworkURL }}" />
</div>
<div class="info">
<span id="name">{{ .Track.Name }}</span>
<div id="artist">{{ .Track.Artist }}</div>
</div>
</div>
</body>
<script>
const fetchCurrentTrack = () =>
fetch("http://localhost:{{ .Port }}/track").then((resp) => resp.json());
const artistEl = document.querySelector("#artist");
const coverEl = document.querySelector("#cover");
const nameEl = document.querySelector("#name");
const render = async () => {
const currentTrack = await fetchCurrentTrack();
if (!currentTrack || !currentTrack.name) return;
coverEl.src = currentTrack.artworkUrl;
nameEl.innerHTML = currentTrack.name;
artistEl.innerHTML = currentTrack.artist;
};
setInterval(render, {{ .Refresh }});
</script>
</html>

119
main.go Normal file
View File

@@ -0,0 +1,119 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"text/template"
"time"
)
const command = `
tell application "Spotify"
set ctrack to "{"
set ctrack to ctrack & "\"artist\": \"" & current track's artist & "\""
set ctrack to ctrack & ",\"album\": \"" & current track's album & "\""
set ctrack to ctrack & ",\"discNumber\": " & current track's disc number
set ctrack to ctrack & ",\"duration\": " & current track's duration
set ctrack to ctrack & ",\"playedCount\": " & current track's played count
set ctrack to ctrack & ",\"trackNumber\": " & current track's track number
set ctrack to ctrack & ",\"popularity\": " & current track's popularity
set ctrack to ctrack & ",\"id\": \"" & current track's id & "\""
set ctrack to ctrack & ",\"position\": " & (player position as integer)
set ctrack to ctrack & ",\"name\": \"" & current track's name & "\""
set ctrack to ctrack & ",\"albumArtist\": \"" & current track's album artist & "\""
set ctrack to ctrack & ",\"artworkUrl\": \"" & current track's artwork url & "\""
set ctrack to ctrack & ",\"spotifyUrl\": \"" & current track's spotify url & "\""
set ctrack to ctrack & "}"
return ctrack
end tell`
type SpotifyCurrentTrack struct {
Artist string `json:"artist"`
Album string `json:"album"`
DiscNumber int `json:"discNumber"`
Duration int `json:"duration"`
PlayedCount int `json:"playedCount"`
TrackNumber int `json:"trackNumber"`
Popularity int `json:"popularity"`
ID string `json:"id"`
Name string `json:"name"`
Position int `json:"position"`
AlbumArtist string `json:"albumArtist"`
ArtworkURL string `json:"artworkUrl"`
SpotifyURL string `json:"spotifyUrl"`
}
type Response struct {
Track SpotifyCurrentTrack
Refresh int64
Port string
}
func getCurrentTrack() SpotifyCurrentTrack {
currentTrack := SpotifyCurrentTrack{}
cmd := exec.Command("osascript", "-e", command)
output, err := cmd.CombinedOutput()
if err != nil {
return currentTrack
}
if err := json.Unmarshal(output, &currentTrack); err != nil {
return currentTrack
}
return currentTrack
}
func main() {
port := flag.String("port", "5783", "http port")
flag.Parse()
ex, _ := os.Executable()
tmpl, err := template.ParseFiles(filepath.Join(ex, "../index.html"))
if err != nil {
panic(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
response := Response{
Track: getCurrentTrack(),
Refresh: time.Second.Milliseconds(),
Port: *port,
}
refreshQueryParam := req.URL.Query().Get("refresh")
if refreshQueryParam != "" {
if duration, err := time.ParseDuration(refreshQueryParam); err == nil {
response.Refresh = duration.Milliseconds()
}
}
if err := tmpl.Execute(w, response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
http.HandleFunc("/track", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
resp, _ := json.Marshal(getCurrentTrack())
fmt.Fprint(w, string(resp))
})
fmt.Printf("The server is running on port %s!", *port)
http.ListenAndServe(fmt.Sprintf(":%s", *port), nil)
}