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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/davecheney/xattr"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func fnNoExt(fileName string) string {
|
||||
return fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
||||
var httpClient = new(http.Client)
|
||||
|
||||
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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []string
|
||||
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
|
||||
queue *Queue
|
||||
ambiance beep.Mixer
|
||||
curamb string
|
||||
curamb Ambiance
|
||||
events events.EventEmmiter
|
||||
next bool
|
||||
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
|
||||
|
||||
import (
|
||||
"dndmusicbot/opus"
|
||||
discordspeaker "dndmusicbot/speaker"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/effects"
|
||||
"github.com/faiface/beep/mp3"
|
||||
"github.com/fhs/gompd/v2/mpd"
|
||||
"github.com/google/uuid"
|
||||
"github.com/kataras/go-events"
|
||||
|
@ -146,11 +145,11 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
var fn string
|
||||
var id string
|
||||
switch data := payload[0].(type) {
|
||||
case json.RawMessage:
|
||||
var err error
|
||||
err = json.Unmarshal(data, &fn)
|
||||
err = json.Unmarshal(data, &id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
@ -160,21 +159,30 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(fmt.Sprintf("./ambiance/%s.mp3", fn))
|
||||
amb, err := GetAmbiance(id)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
play, _, err := mp3.Decode(f)
|
||||
f, err := os.Open(amb.Path)
|
||||
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)
|
||||
|
||||
volume := &effects.Volume{
|
||||
Streamer: loop,
|
||||
Base: 2,
|
||||
Volume: -2,
|
||||
Volume: -2.5,
|
||||
Silent: false,
|
||||
}
|
||||
|
||||
|
@ -187,10 +195,10 @@ func (app *App) ambiancePlay(payload ...interface{}) {
|
|||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
|
||||
app.curamb = fn
|
||||
app.curamb = amb
|
||||
|
||||
msg["event"] = "ambiance_play"
|
||||
out["type"] = fn
|
||||
out["id"] = id
|
||||
msg["payload"] = out
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
@ -235,7 +243,7 @@ func (app *App) ambianceAdd(payload ...interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
err := DownloadAmbiance(amburl, ambtitle)
|
||||
amb, err := AddAmbiance(amburl, ambtitle)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
@ -244,7 +252,8 @@ func (app *App) ambianceAdd(payload ...interface{}) {
|
|||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_add"
|
||||
out["type"] = ambtitle
|
||||
out["title"] = amb.Title
|
||||
out["id"] = amb.Id
|
||||
msg["payload"] = out
|
||||
ws_msg <- msg
|
||||
}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -6,6 +6,7 @@ go 1.19
|
|||
|
||||
require (
|
||||
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/fhs/gompd/v2 v2.3.0
|
||||
github.com/gohugoio/hugo v0.106.0
|
||||
|
@ -15,13 +16,16 @@ require (
|
|||
github.com/jackc/pgx/v5 v5.1.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/kataras/go-events v0.0.3
|
||||
github.com/peterhellberg/link v1.2.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sosodev/duration v1.0.1
|
||||
github.com/spf13/afero v1.9.3
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/time v0.2.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
|
||||
)
|
||||
|
||||
|
@ -75,7 +79,6 @@ require (
|
|||
github.com/yuin/goldmark v1.5.3 // indirect
|
||||
go.opencensus.io v0.24.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/sys v0.2.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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
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/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
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-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-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-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=
|
||||
|
@ -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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/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/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/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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) {
|
||||
tmp := make([]byte, m.f.NumChannels+2)
|
||||
|
||||
status, err := app.mpd.Status()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
for i := range samples {
|
||||
if status["state"] != "play" {
|
||||
samples[i] = [2]float64{}
|
||||
ok = true
|
||||
continue
|
||||
}
|
||||
dn, err := m.file.Read(tmp)
|
||||
if dn == len(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"
|
||||
)
|
||||
|
||||
type Ambiance struct {
|
||||
Type string
|
||||
URL string
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.Println("queue.go loading..")
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ func init() {
|
|||
|
||||
type IndexData struct {
|
||||
Playlists []Playlist
|
||||
Ambiance []string
|
||||
Ambiance []Ambiance
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
amblist, err := GetAmbiance()
|
||||
amblist, err := GetAmbiances()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
|
|
@ -107,9 +107,27 @@ window.onload = function () {
|
|||
ws.onmessage = (e) => {
|
||||
data = JSON.parse(e.data)
|
||||
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":
|
||||
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'
|
||||
break
|
||||
case "ambiance_stop":
|
||||
|
@ -172,6 +190,9 @@ window.onload = function () {
|
|||
"url": url.value
|
||||
}
|
||||
}))
|
||||
|
||||
title.value = ""
|
||||
url.value = ""
|
||||
})
|
||||
|
||||
addInteractHandler(submit, (e, isTouch) => {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<title>D&D Music Bot!</title>
|
||||
<link rel="stylesheet" href="/css/solarized-dark.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>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
@ -52,7 +53,7 @@
|
|||
<section>
|
||||
<div id="ambiance" class="item-container">
|
||||
{{ range .Ambiance }}
|
||||
<div class="item drag" data-id="{{ . }}">{{ . }}</div>
|
||||
<div class="item drag" data-id="{{ .Id }}">{{ .Title }}</div>
|
||||
{{ end}}
|
||||
<div class="item locked stop" data-id="reset">Stop</div>
|
||||
</div>
|
||||
|
|
2
ws.go
2
ws.go
|
@ -73,7 +73,7 @@ func handleWS(c *websocket.Conn) error {
|
|||
msg := make(map[string]interface{})
|
||||
out := make(map[string]interface{})
|
||||
msg["event"] = "ambiance_play"
|
||||
out["type"] = app.curamb
|
||||
out["id"] = app.curamb.Id
|
||||
msg["payload"] = out
|
||||
c.WriteJSON(msg)
|
||||
} else {
|
||||
|
|
39
ytdl.go
39
ytdl.go
|
@ -1,16 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
|
@ -19,7 +15,7 @@ var yturl = "https://youtu.be/%s"
|
|||
|
||||
func NewYTdl(vid string) ([]byte, error) {
|
||||
ytdl := config.GetString("youtube.ytdl")
|
||||
uri, err := exec.Command(
|
||||
yt := exec.Command(
|
||||
ytdl,
|
||||
fmt.Sprintf(yturl, vid),
|
||||
"--cookies", "./cookies.txt",
|
||||
|
@ -30,7 +26,11 @@ func NewYTdl(vid string) ([]byte, error) {
|
|||
"--restrict-filenames",
|
||||
"-f", "251",
|
||||
"--get-url",
|
||||
).Output()
|
||||
)
|
||||
|
||||
yt.Stderr = os.Stderr
|
||||
|
||||
uri, err := yt.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -38,15 +38,25 @@ func NewYTdl(vid string) ([]byte, error) {
|
|||
return uri[:len(uri)-1], nil
|
||||
}
|
||||
|
||||
func DownloadAmbiance(uri string, name string) error {
|
||||
ytdl := config.GetString("youtube.ytdl")
|
||||
|
||||
tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.aac").Output()
|
||||
func YTUrl(uri string) (vid string, err error) {
|
||||
u, err := url.Parse(uri)
|
||||
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(
|
||||
ytdl,
|
||||
|
@ -55,7 +65,7 @@ func DownloadAmbiance(uri string, name string) error {
|
|||
"--no-cache-dir",
|
||||
"-f", "140",
|
||||
"--cookies", "../cookies.txt",
|
||||
"-o", string(tmpfile),
|
||||
"-o", "-",
|
||||
"--force-overwrites",
|
||||
"-q",
|
||||
"--progress",
|
||||
|
@ -180,3 +190,4 @@ func DownloadAmbiance(uri string, name string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue