Compare commits
3 Commits
f2063f5d18
...
7f288e541d
Author | SHA1 | Date |
---|---|---|
Stein Ivar Berghei | 7f288e541d | |
Stein Ivar Berghei | 2eb108d185 | |
Stein Ivar Berghei | 45f9c0024a |
204
ambiance.go
204
ambiance.go
|
@ -1,24 +1,216 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/davecheney/xattr"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fnNoExt(fileName string) string {
|
var httpClient = new(http.Client)
|
||||||
return fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
|
||||||
|
type Ambiance struct {
|
||||||
|
Id string
|
||||||
|
Title string
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAmbiance() ([]string, error) {
|
func GetAmbiance(id string) (amb Ambiance, err error) {
|
||||||
|
fp := filepath.Join("./ambiance", id+".opus")
|
||||||
|
_, err = os.Stat(fp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
title, err := xattr.Getxattr(fp, "title")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ambiance{
|
||||||
|
Id: id,
|
||||||
|
Title: string(title),
|
||||||
|
Path: fp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAmbiances() (amb []Ambiance, err error) {
|
||||||
files, err := os.ReadDir("./ambiance")
|
files, err := os.ReadDir("./ambiance")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var out []string
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
out = append(out, fnNoExt(file.Name()))
|
title, err := xattr.Getxattr(filepath.Join("./ambiance", file.Name()), "title")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
|
||||||
|
}
|
||||||
|
amb = append(amb, Ambiance{
|
||||||
|
Id: file.Name()[:len(file.Name())-len(filepath.Ext(file.Name()))],
|
||||||
|
Title: string(title),
|
||||||
|
Path: filepath.Join("./ambiance", file.Name()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddAmbiance(uri, title string) (Ambiance, error) {
|
||||||
|
var amb Ambiance
|
||||||
|
|
||||||
|
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.opus").Output()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpfile = tmpfile[:len(tmpfile)-1]
|
||||||
|
|
||||||
|
vid, err := YTUrl(uri)
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Start YTdl for %s", uri)
|
||||||
|
dluri, err := NewYTdl(vid)
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Start download.. %s", uri)
|
||||||
|
resp, err := httpClient.Get(string(dluri))
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
ff := exec.Command(
|
||||||
|
"ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-i", "-",
|
||||||
|
"-vn",
|
||||||
|
"-acodec", "copy",
|
||||||
|
"-movflags", "+faststart",
|
||||||
|
"-t", "01:00:00",
|
||||||
|
"-v", "error",
|
||||||
|
// "-stats",
|
||||||
|
"-progress", "pipe:1",
|
||||||
|
// "-af", "loudnorm=I=-16:LRA=11:TP=-1.5",
|
||||||
|
string(tmpfile),
|
||||||
|
)
|
||||||
|
|
||||||
|
ffprogress, err := ff.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ff.Stderr = os.Stderr
|
||||||
|
ff.Stdin = resp.Body
|
||||||
|
|
||||||
|
err = ff.Start()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile))
|
||||||
|
msg := make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_encode_start"
|
||||||
|
data := make(map[string]string)
|
||||||
|
data["name"] = title
|
||||||
|
msg["payload"] = data
|
||||||
|
ws_msg <- msg
|
||||||
|
|
||||||
|
msg = make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_encode_progress"
|
||||||
|
data = make(map[string]string)
|
||||||
|
data["name"] = title
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(ffprogress)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
p := strings.Split(scanner.Text(), "=")
|
||||||
|
if len(p) == 2 {
|
||||||
|
data[p[0]] = strings.TrimSpace(p[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
prate.Do(func() {
|
||||||
|
msg["payload"] = data
|
||||||
|
ws_msg <- msg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ff.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = make(map[string]interface{})
|
||||||
|
msg["event"] = "ambiance_encode_complete"
|
||||||
|
data = make(map[string]string)
|
||||||
|
data["name"] = title
|
||||||
|
msg["payload"] = data
|
||||||
|
ws_msg <- msg
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
fn := filepath.Join("./ambiance", fmt.Sprintf("%s.opus", id.String()))
|
||||||
|
|
||||||
|
log.Printf("Moving to %s", fn)
|
||||||
|
|
||||||
|
in, err := os.Open(string(tmpfile))
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
of, err := os.Create(fn)
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(of, in)
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = of.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = of.Close()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = in.Close()
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(string(tmpfile))
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Setting xattr")
|
||||||
|
err = xattr.Setxattr(fn, "title", []byte(title))
|
||||||
|
if err != nil {
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amb.Id = id.String()
|
||||||
|
amb.Title = title
|
||||||
|
|
||||||
|
log.Println("Return info.")
|
||||||
|
return amb, nil
|
||||||
}
|
}
|
||||||
|
|
2
bot.go
2
bot.go
|
@ -52,7 +52,7 @@ type App struct {
|
||||||
youtube *youtube.Service
|
youtube *youtube.Service
|
||||||
queue *Queue
|
queue *Queue
|
||||||
ambiance beep.Mixer
|
ambiance beep.Mixer
|
||||||
curamb string
|
curamb Ambiance
|
||||||
events events.EventEmmiter
|
events events.EventEmmiter
|
||||||
next bool
|
next bool
|
||||||
db *pgx.Conn
|
db *pgx.Conn
|
||||||
|
|
|
@ -1,272 +0,0 @@
|
||||||
package discord
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/kataras/go-events"
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
GATEWAYBOTURL = "https://discord.com/api/gateway/bot"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PermissionVoiceConnect = 0x0000000000100000
|
|
||||||
PermissionVoiceSpeak = 0x0000000000200000
|
|
||||||
PermissionSendMessages = 0x0000000000000800
|
|
||||||
)
|
|
||||||
|
|
||||||
type Connection struct {
|
|
||||||
events events.EventEmmiter
|
|
||||||
httpClient *http.Client
|
|
||||||
dialer *websocket.Dialer
|
|
||||||
conn *websocket.Conn
|
|
||||||
token string
|
|
||||||
seq int
|
|
||||||
ready chan bool
|
|
||||||
vstateUpdate chan bool
|
|
||||||
vserverUpdate chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoiceConnection struct {
|
|
||||||
d *Connection
|
|
||||||
conn *websocket.Conn
|
|
||||||
sid string
|
|
||||||
e string
|
|
||||||
}
|
|
||||||
|
|
||||||
type VoiceStateUpdate struct {
|
|
||||||
Op int `json:"op"`
|
|
||||||
Data struct {
|
|
||||||
Guild string `json:"guild_id"`
|
|
||||||
Channel string `json:"channel_id"`
|
|
||||||
Mute bool `json:"self_mute"`
|
|
||||||
Deaf bool `json:"self_dead"`
|
|
||||||
} `json:"d"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Identify struct {
|
|
||||||
Op int `json:"op"`
|
|
||||||
|
|
||||||
Data struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
Intents int `json:"intents"`
|
|
||||||
|
|
||||||
Properties struct {
|
|
||||||
OS string `json:"os"`
|
|
||||||
Browser string `json:"browser"`
|
|
||||||
Device string `json:"device"`
|
|
||||||
} `json:"properties"`
|
|
||||||
} `json:"d"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnection(token string) (*Connection, error) {
|
|
||||||
conn := new(Connection)
|
|
||||||
|
|
||||||
conn.token = token
|
|
||||||
|
|
||||||
conn.ready = make(chan bool)
|
|
||||||
conn.vserverUpdate = make(chan bool)
|
|
||||||
conn.vstateUpdate = make(chan bool)
|
|
||||||
|
|
||||||
conn.events = events.New()
|
|
||||||
conn.events.On("READY", conn.eventready)
|
|
||||||
|
|
||||||
conn.dialer = &websocket.Dialer{
|
|
||||||
HandshakeTimeout: 45 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.httpClient = &http.Client{}
|
|
||||||
gw, err := conn.GetGateway()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.conn, _, err = conn.dialer.Dial(gw, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
_, b, err := conn.conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
go conn.handleGatewayEvent(b)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Identify
|
|
||||||
id := new(Identify)
|
|
||||||
id.Op = 2
|
|
||||||
id.Data.Token = token
|
|
||||||
id.Data.Intents = PermissionVoiceConnect | PermissionVoiceSpeak | PermissionSendMessages
|
|
||||||
id.Data.Properties.OS = "linux"
|
|
||||||
id.Data.Properties.Browser = "dndmusicbot/0.0.1"
|
|
||||||
id.Data.Properties.Device = "dndmusicbot"
|
|
||||||
|
|
||||||
json.NewEncoder(os.Stdout).Encode(id)
|
|
||||||
|
|
||||||
err = conn.conn.WriteJSON(id)
|
|
||||||
if err != nil {
|
|
||||||
conn.conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Wait for Ready!
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, err
|
|
||||||
case <-conn.ready:
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) NewVoiceConnection(guild string, channel string) (*VoiceConnection, error) {
|
|
||||||
vc := new(VoiceConnection)
|
|
||||||
vc.d = c
|
|
||||||
|
|
||||||
c.events.On("VOICE_STATE_UPDATE", vc.voiceStateUpdate)
|
|
||||||
c.events.On("VOICE_SERVER_UDPATE", vc.voiceServerUpdate)
|
|
||||||
|
|
||||||
msg := new(VoiceStateUpdate)
|
|
||||||
msg.Op = 4
|
|
||||||
msg.Data.Guild = guild
|
|
||||||
msg.Data.Channel = channel
|
|
||||||
msg.Data.Mute = false
|
|
||||||
msg.Data.Deaf = true
|
|
||||||
|
|
||||||
c.conn.WriteJSON(msg)
|
|
||||||
|
|
||||||
var state, server bool
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// We have to wait for both a vserverupdate and a vstateupdate before we continue.
|
|
||||||
for !state && !server {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case <-c.vserverUpdate:
|
|
||||||
server = true
|
|
||||||
case <-c.vstateUpdate:
|
|
||||||
state = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should have gotten all the information by now.. continue..
|
|
||||||
|
|
||||||
return vc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vc *VoiceConnection) voiceStateUpdate(payload ...interface{}) {
|
|
||||||
vc.d.vstateUpdate <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vc *VoiceConnection) voiceServerUpdate(payload ...interface{}) {
|
|
||||||
vc.d.vserverUpdate <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ready event. Contains alot of information regarding our session.
|
|
||||||
func (c *Connection) eventready(payload ...interface{}) {
|
|
||||||
c.ready <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) handleGatewayEvent(js []byte) error {
|
|
||||||
eventdata := gjson.GetManyBytes(js, "op", "d", "s", "t")
|
|
||||||
|
|
||||||
op := eventdata[0]
|
|
||||||
data := eventdata[1]
|
|
||||||
seq := eventdata[2]
|
|
||||||
eventname := eventdata[3]
|
|
||||||
|
|
||||||
if seq.Exists() {
|
|
||||||
c.seq = int(seq.Int())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
c.conn.SetCloseHandler(func(code int, text string) error {
|
|
||||||
cancel()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
switch op.Int() {
|
|
||||||
case 0:
|
|
||||||
fmt.Printf("Event: %s Data: %+v\n", eventname.String(), data)
|
|
||||||
c.events.Emit(events.EventName(eventname.String()), data)
|
|
||||||
case 10: // Hello
|
|
||||||
hb_interval, err := time.ParseDuration(fmt.Sprintf("%dms", data.Get("heartbeat_interval").Int()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("We Received a Hello! Starting heartbeat on %.2fs interval\n", hb_interval.Seconds())
|
|
||||||
|
|
||||||
hb_ticker := time.NewTicker(hb_interval)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
break
|
|
||||||
case <-hb_ticker.C:
|
|
||||||
msg := make(map[string]interface{})
|
|
||||||
msg["op"] = 1
|
|
||||||
if c.seq == 0 {
|
|
||||||
msg["d"] = nil
|
|
||||||
} else {
|
|
||||||
msg["d"] = c.seq
|
|
||||||
}
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
log.Printf("Sending Heartbeat: %+v\n", msg)
|
|
||||||
err = c.conn.WriteJSON(msg)
|
|
||||||
if err != nil {
|
|
||||||
c.conn.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 11:
|
|
||||||
log.Println("Heartbeat ACK!")
|
|
||||||
default:
|
|
||||||
log.Printf("%d: %+v\n", op.Int(), data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) GetGateway() (string, error) {
|
|
||||||
req, err := http.NewRequest("GET", GATEWAYBOTURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", "Bot "+c.token)
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gjson.GetBytes(b, "url").String(), err
|
|
||||||
}
|
|
35
events.go
35
events.go
|
@ -1,9 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"dndmusicbot/opus"
|
||||||
discordspeaker "dndmusicbot/speaker"
|
discordspeaker "dndmusicbot/speaker"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -12,7 +12,6 @@ import (
|
||||||
|
|
||||||
"github.com/faiface/beep"
|
"github.com/faiface/beep"
|
||||||
"github.com/faiface/beep/effects"
|
"github.com/faiface/beep/effects"
|
||||||
"github.com/faiface/beep/mp3"
|
|
||||||
"github.com/fhs/gompd/v2/mpd"
|
"github.com/fhs/gompd/v2/mpd"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/kataras/go-events"
|
"github.com/kataras/go-events"
|
||||||
|
@ -146,11 +145,11 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var fn string
|
var id string
|
||||||
switch data := payload[0].(type) {
|
switch data := payload[0].(type) {
|
||||||
case json.RawMessage:
|
case json.RawMessage:
|
||||||
var err error
|
var err error
|
||||||
err = json.Unmarshal(data, &fn)
|
err = json.Unmarshal(data, &id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -160,21 +159,30 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(fmt.Sprintf("./ambiance/%s.mp3", fn))
|
amb, err := GetAmbiance(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
play, _, err := mp3.Decode(f)
|
f, err := os.Open(amb.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
play, err := opus.New(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loop := beep.Loop(-1, play)
|
loop := beep.Loop(-1, play)
|
||||||
|
|
||||||
volume := &effects.Volume{
|
volume := &effects.Volume{
|
||||||
Streamer: loop,
|
Streamer: loop,
|
||||||
Base: 2,
|
Base: 2,
|
||||||
Volume: -2,
|
Volume: -2.5,
|
||||||
Silent: false,
|
Silent: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,10 +195,10 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
||||||
msg := make(map[string]interface{})
|
msg := make(map[string]interface{})
|
||||||
out := make(map[string]interface{})
|
out := make(map[string]interface{})
|
||||||
|
|
||||||
app.curamb = fn
|
app.curamb = amb
|
||||||
|
|
||||||
msg["event"] = "ambiance_play"
|
msg["event"] = "ambiance_play"
|
||||||
out["type"] = fn
|
out["id"] = id
|
||||||
msg["payload"] = out
|
msg["payload"] = out
|
||||||
ws_msg <- msg
|
ws_msg <- msg
|
||||||
}
|
}
|
||||||
|
@ -235,7 +243,7 @@ func (app *App) ambianceAdd(payload ...interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := DownloadAmbiance(amburl, ambtitle)
|
amb, err := AddAmbiance(amburl, ambtitle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -244,7 +252,8 @@ func (app *App) ambianceAdd(payload ...interface{}) {
|
||||||
msg := make(map[string]interface{})
|
msg := make(map[string]interface{})
|
||||||
out := make(map[string]interface{})
|
out := make(map[string]interface{})
|
||||||
msg["event"] = "ambiance_add"
|
msg["event"] = "ambiance_add"
|
||||||
out["type"] = ambtitle
|
out["title"] = amb.Title
|
||||||
|
out["id"] = amb.Id
|
||||||
msg["payload"] = out
|
msg["payload"] = out
|
||||||
ws_msg <- msg
|
ws_msg <- msg
|
||||||
}
|
}
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -6,6 +6,7 @@ go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bwmarrin/discordgo v0.26.1
|
github.com/bwmarrin/discordgo v0.26.1
|
||||||
|
github.com/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b
|
||||||
github.com/faiface/beep v1.1.0
|
github.com/faiface/beep v1.1.0
|
||||||
github.com/fhs/gompd/v2 v2.3.0
|
github.com/fhs/gompd/v2 v2.3.0
|
||||||
github.com/gohugoio/hugo v0.106.0
|
github.com/gohugoio/hugo v0.106.0
|
||||||
|
@ -15,13 +16,16 @@ require (
|
||||||
github.com/jackc/pgx/v5 v5.1.0
|
github.com/jackc/pgx/v5 v5.1.0
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/kataras/go-events v0.0.3
|
github.com/kataras/go-events v0.0.3
|
||||||
|
github.com/peterhellberg/link v1.2.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/sosodev/duration v1.0.1
|
github.com/sosodev/duration v1.0.1
|
||||||
github.com/spf13/afero v1.9.3
|
github.com/spf13/afero v1.9.3
|
||||||
github.com/spf13/viper v1.14.0
|
github.com/spf13/viper v1.14.0
|
||||||
github.com/tidwall/gjson v1.14.3
|
github.com/tidwall/gjson v1.14.3
|
||||||
|
golang.org/x/net v0.2.0
|
||||||
golang.org/x/time v0.2.0
|
golang.org/x/time v0.2.0
|
||||||
google.golang.org/api v0.103.0
|
google.golang.org/api v0.103.0
|
||||||
|
gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0
|
||||||
layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32
|
layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +79,6 @@ require (
|
||||||
github.com/yuin/goldmark v1.5.3 // indirect
|
github.com/yuin/goldmark v1.5.3 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.2.0 // indirect
|
golang.org/x/crypto v0.2.0 // indirect
|
||||||
golang.org/x/net v0.2.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||||
golang.org/x/sys v0.2.0 // indirect
|
golang.org/x/sys v0.2.0 // indirect
|
||||||
golang.org/x/text v0.4.0 // indirect
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
|
13
go.sum
13
go.sum
|
@ -91,11 +91,11 @@ github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozb
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b h1:a/CjIrvEH2NkUUIo4sqWIw+h3E63ttmS8L8Vx3ZaLS0=
|
||||||
|
github.com/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b/go.mod h1:Gc/R1HBRJIEElnD4PGXGQZQYMb14oPbvTovm6WeuAvk=
|
||||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
|
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8 h1:t1Ox7k2+GSzIv3fihjV7YFGb40nb/e2oyrTM/ngbzbA=
|
|
||||||
github.com/dpup/gohubbub v0.0.0-20140517235056-2dc6969d22d8/go.mod h1:QqXVl9BAyVoWIZE4oA9XfkwCjQ3JaajiX4vq7Zh8Vzs=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
@ -277,6 +277,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||||
|
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
|
||||||
|
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -284,8 +286,6 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/r3labs/sse/v2 v2.8.2 h1:YWZy2i2nLoD5fE3vLLTdTz/8wxIYIFp5XbLNmmrrNts=
|
|
||||||
github.com/r3labs/sse/v2 v2.8.2/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
@ -406,7 +406,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -663,14 +662,14 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
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 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
|
||||||
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY=
|
gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY=
|
||||||
gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg=
|
gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg=
|
||||||
|
gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0 h1:B8lK1KhYrE4H3urNYBAL/UquYftW65IHPY8JP3gpZ4M=
|
||||||
|
gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
|
gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
|
||||||
|
|
10
mpd.go
10
mpd.go
|
@ -145,7 +145,17 @@ func (m *MPD) Err() error {
|
||||||
func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) {
|
func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
tmp := make([]byte, m.f.NumChannels+2)
|
tmp := make([]byte, m.f.NumChannels+2)
|
||||||
|
|
||||||
|
status, err := app.mpd.Status()
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
for i := range samples {
|
for i := range samples {
|
||||||
|
if status["state"] != "play" {
|
||||||
|
samples[i] = [2]float64{}
|
||||||
|
ok = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
dn, err := m.file.Read(tmp)
|
dn, err := m.file.Read(tmp)
|
||||||
if dn == len(tmp) {
|
if dn == len(tmp) {
|
||||||
samples[i], _ = m.f.DecodeSigned(tmp)
|
samples[i], _ = m.f.DecodeSigned(tmp)
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package opus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/faiface/beep"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
opusd "gopkg.in/hraban/opus.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
rc io.ReadSeekCloser
|
||||||
|
r *opusd.Stream
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(f io.ReadSeekCloser) (beep.StreamSeeker, error) {
|
||||||
|
d := new(decoder)
|
||||||
|
st, err := opusd.NewStream(f)
|
||||||
|
if err != nil {
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.rc = f
|
||||||
|
d.r = st
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *decoder) Err() error { return s.err }
|
||||||
|
|
||||||
|
func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) {
|
||||||
|
if d.err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
var tmp [2]float32
|
||||||
|
for i := range samples {
|
||||||
|
dn, err := d.r.ReadFloat32(tmp[:])
|
||||||
|
|
||||||
|
if dn == 1 {
|
||||||
|
samples[i][0], samples[i][1] = float64(tmp[0]), float64(tmp[1])
|
||||||
|
n++
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ok = false
|
||||||
|
d.err = errors.Wrap(err, "ogg/opus")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Len() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Position() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) Seek(p int) error {
|
||||||
|
_, err := d.rc.Seek(int64(p), io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return errors.Wrap(err, "ogg/opus")
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err := opusd.NewStream(d.rc)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return errors.Wrap(err, "ogg/opus")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.r = st
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package opus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/faiface/beep"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
mixer beep.Mixer
|
||||||
|
frameSize int = 960
|
||||||
|
samples = make([][2]float64, frameSize)
|
||||||
|
buf []byte
|
||||||
|
maxBytes int = (frameSize * 2) * 2
|
||||||
|
done chan struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func update() {
|
||||||
|
mu.Lock()
|
||||||
|
mixer.Stream(samples)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
for i := range samples {
|
||||||
|
for c := range samples[i] {
|
||||||
|
val := samples[i][c]
|
||||||
|
if val < -1 {
|
||||||
|
val = -1
|
||||||
|
}
|
||||||
|
if val > +1 {
|
||||||
|
val = +1
|
||||||
|
}
|
||||||
|
valInt16 := int16(val * (1<<15 - 1))
|
||||||
|
low := byte(valInt16)
|
||||||
|
high := byte(valInt16 >> 8)
|
||||||
|
buf[i*4+c*2+0] = low
|
||||||
|
buf[i*4+c*2+1] = high
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("%+v", buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(t *testing.T) {
|
||||||
|
buf = make([]byte, maxBytes)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*9)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
file, err := os.Open("test.opus")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := New(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := beep.Loop(-1, d)
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
mixer.Add(loop)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
update()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
queue.go
5
queue.go
|
@ -13,11 +13,6 @@ import (
|
||||||
discordspeaker "dndmusicbot/speaker"
|
discordspeaker "dndmusicbot/speaker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ambiance struct {
|
|
||||||
Type string
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.Println("queue.go loading..")
|
log.Println("queue.go loading..")
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ func init() {
|
||||||
|
|
||||||
type IndexData struct {
|
type IndexData struct {
|
||||||
Playlists []Playlist
|
Playlists []Playlist
|
||||||
Ambiance []string
|
Ambiance []Ambiance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app App) ServeFiles(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
func (app App) ServeFiles(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
@ -79,7 +79,7 @@ func (app App) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params
|
||||||
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Unable to get playlists. "+err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
amblist, err := GetAmbiance()
|
amblist, err := GetAmbiances()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -107,9 +107,27 @@ window.onload = function () {
|
||||||
ws.onmessage = (e) => {
|
ws.onmessage = (e) => {
|
||||||
data = JSON.parse(e.data)
|
data = JSON.parse(e.data)
|
||||||
switch (data.event) {
|
switch (data.event) {
|
||||||
|
case "ambiance_add":
|
||||||
|
const container = document.querySelector("#ambiance")
|
||||||
|
var newdiv = document.createElement('div');
|
||||||
|
newdiv.className = "item"
|
||||||
|
newdiv.dataset.id = data.payload.id
|
||||||
|
newdiv.innerText = data.payload.title
|
||||||
|
addInteractHandler(newdiv, (e, isTouch) => {
|
||||||
|
isTouch && e.preventDefault()
|
||||||
|
|
||||||
|
var id = e.target.dataset.id
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"event": ((id === "reset") ? "ambiance_stop" : "ambiance_play"),
|
||||||
|
"payload": id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
container.insertBefore(newdiv, document.querySelector("#ambiance div:last-child"))
|
||||||
|
break
|
||||||
case "ambiance_play":
|
case "ambiance_play":
|
||||||
document.querySelectorAll("#ambiance > div").forEach((e) => {e.style.removeProperty("background-color")})
|
document.querySelectorAll("#ambiance > div").forEach((e) => {e.style.removeProperty("background-color")})
|
||||||
document.querySelector(`#ambiance > div[data-id='${data.payload.type}']`).style.backgroundColor = "burlywood"
|
document.querySelector(`#ambiance > div[data-id='${data.payload.id}']`).style.backgroundColor = "burlywood"
|
||||||
ambiance.style.pointerEvents = 'auto'
|
ambiance.style.pointerEvents = 'auto'
|
||||||
break
|
break
|
||||||
case "ambiance_stop":
|
case "ambiance_stop":
|
||||||
|
@ -172,6 +190,9 @@ window.onload = function () {
|
||||||
"url": url.value
|
"url": url.value
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
title.value = ""
|
||||||
|
url.value = ""
|
||||||
})
|
})
|
||||||
|
|
||||||
addInteractHandler(submit, (e, isTouch) => {
|
addInteractHandler(submit, (e, isTouch) => {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<title>D&D Music Bot!</title>
|
<title>D&D Music Bot!</title>
|
||||||
<link rel="stylesheet" href="/css/solarized-dark.css">
|
<link rel="stylesheet" href="/css/solarized-dark.css">
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAACNklEQVQ4jaWSy0vUcRTFP/f7G4cUUqzAZtQsgzYWiDMjjYgotbOtYLSIFlKBix7YH9DWXQaRFRREi8hN9MCFPZDUzB4IUkRo5Oho1CLTcEbne1o0UWCI0FndC+cezr33wH/C/m5mkvFOw04JIsBrmXVVDI0OryfgfhepZH0bWA/wFaMLqDXp/nQyuWVDAhi1wIz36k5n/BXgMVAamI+vJxD6o7TS61Uw7QLbEXH+OGIOQzmtTG7IgVfBPjOdE6oXdtrQd8mOVQ6/+vCbM9XcvGmipia8xsFsLLZN6DBQZWIRKJPZISd5gLFYrCBaYF3KLB0IFxdVzyQTZYZukNXZEEA2CDaH8SUS5zHbBVZo+AE5OzrXEL+eyxHxZo2IyzL/FqywcnhsFCCktrYgnZrqWc35nsCCqHM2mJMWkXuD+RVPUIvzRxB3K0Ze3F5zg8/pj1XCEkEQ9OHoZnH5uaFAYfXjrcmhUkPvyit39v7zC9lcKO1Y3QoEwOD28fEl4CRAqiFxJyddAHuUnpwsnW9MFIdW3JeMW41Ghl++N/AOMuX5YTCqlU/nbCxWZLDXQRfQRNiNr+Z0L+OUFHZwNhm/CRBaWMh8Ki0u+iYoQeyZ3Z+4lcKeytSBqPOyZ2a+X1grWESoE6wFWAZwNRMTWaEzBk/yLtrNdAmow7gaHRm96HEpYCC/divGjGEdv+h5qLk5lM4utUvWAvxw8g8iI2MPAeYbE9VeFMrbNZl2GzoRHRrrWy+hG8ZPg13xb+XS+CIAAAAASUVORK5CYII=">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -52,7 +53,7 @@
|
||||||
<section>
|
<section>
|
||||||
<div id="ambiance" class="item-container">
|
<div id="ambiance" class="item-container">
|
||||||
{{ range .Ambiance }}
|
{{ range .Ambiance }}
|
||||||
<div class="item drag" data-id="{{ . }}">{{ . }}</div>
|
<div class="item drag" data-id="{{ .Id }}">{{ .Title }}</div>
|
||||||
{{ end}}
|
{{ end}}
|
||||||
<div class="item locked stop" data-id="reset">Stop</div>
|
<div class="item locked stop" data-id="reset">Stop</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
2
ws.go
2
ws.go
|
@ -73,7 +73,7 @@ func handleWS(c *websocket.Conn) error {
|
||||||
msg := make(map[string]interface{})
|
msg := make(map[string]interface{})
|
||||||
out := make(map[string]interface{})
|
out := make(map[string]interface{})
|
||||||
msg["event"] = "ambiance_play"
|
msg["event"] = "ambiance_play"
|
||||||
out["type"] = app.curamb
|
out["id"] = app.curamb.Id
|
||||||
msg["payload"] = out
|
msg["payload"] = out
|
||||||
c.WriteJSON(msg)
|
c.WriteJSON(msg)
|
||||||
} else {
|
} else {
|
||||||
|
|
39
ytdl.go
39
ytdl.go
|
@ -1,16 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,7 +15,7 @@ var yturl = "https://youtu.be/%s"
|
||||||
|
|
||||||
func NewYTdl(vid string) ([]byte, error) {
|
func NewYTdl(vid string) ([]byte, error) {
|
||||||
ytdl := config.GetString("youtube.ytdl")
|
ytdl := config.GetString("youtube.ytdl")
|
||||||
uri, err := exec.Command(
|
yt := exec.Command(
|
||||||
ytdl,
|
ytdl,
|
||||||
fmt.Sprintf(yturl, vid),
|
fmt.Sprintf(yturl, vid),
|
||||||
"--cookies", "./cookies.txt",
|
"--cookies", "./cookies.txt",
|
||||||
|
@ -30,7 +26,11 @@ func NewYTdl(vid string) ([]byte, error) {
|
||||||
"--restrict-filenames",
|
"--restrict-filenames",
|
||||||
"-f", "251",
|
"-f", "251",
|
||||||
"--get-url",
|
"--get-url",
|
||||||
).Output()
|
)
|
||||||
|
|
||||||
|
yt.Stderr = os.Stderr
|
||||||
|
|
||||||
|
uri, err := yt.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -38,15 +38,25 @@ func NewYTdl(vid string) ([]byte, error) {
|
||||||
return uri[:len(uri)-1], nil
|
return uri[:len(uri)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DownloadAmbiance(uri string, name string) error {
|
func YTUrl(uri string) (vid string, err error) {
|
||||||
ytdl := config.GetString("youtube.ytdl")
|
u, err := url.Parse(uri)
|
||||||
|
|
||||||
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.aac").Output()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile = tmpfile[:len(tmpfile)-1]
|
switch u.Host {
|
||||||
|
case "youtu.be":
|
||||||
|
vid = u.Path[1:]
|
||||||
|
case "youtube.com":
|
||||||
|
vid = u.Query().Get("v")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func DownloadAmbiance(uri string, name string) error {
|
||||||
|
ytdl := config.GetString("youtube.ytdl")
|
||||||
|
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
ytdl,
|
ytdl,
|
||||||
|
@ -55,7 +65,7 @@ func DownloadAmbiance(uri string, name string) error {
|
||||||
"--no-cache-dir",
|
"--no-cache-dir",
|
||||||
"-f", "140",
|
"-f", "140",
|
||||||
"--cookies", "../cookies.txt",
|
"--cookies", "../cookies.txt",
|
||||||
"-o", string(tmpfile),
|
"-o", "-",
|
||||||
"--force-overwrites",
|
"--force-overwrites",
|
||||||
"-q",
|
"-q",
|
||||||
"--progress",
|
"--progress",
|
||||||
|
@ -180,3 +190,4 @@ func DownloadAmbiance(uri string, name string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in New Issue