Skip to content
Snippets Groups Projects
Commit 8ad92217 authored by ale's avatar ale
Browse files

add a simple MPD server

parent c19f2f79
No related branches found
No related tags found
No related merge requests found
package main
import (
"flag"
"fmt"
"log"
"git.autistici.org/ale/gompd"
"git.autistici.org/ale/djrandom/client/mpd"
)
var (
port = flag.Int("port", 6600, "Port to listen on (MPD)")
)
func main() {
flag.Parse()
db := djmpd.NewDJRandomDatabase()
m := mpd.NewMpd(mpd.NewPortAudioPlayer(), db)
m.HandleURL("djrandom", db)
log.Fatal(m.ListenAndServe(fmt.Sprintf(":%d", *port)))
}
package djmpd
import (
"bytes"
"errors"
"flag"
"fmt"
"io"
"log"
"net/url"
"strings"
"sync"
"git.autistici.org/ale/djrandom/api"
"git.autistici.org/ale/djrandom/client"
"git.autistici.org/ale/djrandom/util"
"git.autistici.org/ale/djrandom/util/config"
"git.autistici.org/ale/gompd"
)
var (
configFile = flag.String("djrandom-config", util.ExpandTilde("~/.djrandom.conf"), "Config file location")
serverUrl = config.String("djrandom-server", "https://djrandom.incal.net", "Server URL")
authKeyId = config.String("auth_key", "", "API authentication key")
authKeySecret = config.String("auth_secret", "", "API authentication secret")
djrandomOnce sync.Once
)
type DJRandomSong struct {
*api.Song
audiofile *api.AudioFile
client *util.HttpClient
}
func newDJRandomSong(song *api.Song, client *util.HttpClient) *DJRandomSong {
af := song.GetBestAudioFile()
return &DJRandomSong{
Song: song,
audiofile: af,
client: client,
}
}
func (s *DJRandomSong) Channels() int {
if s.audiofile != nil {
return s.audiofile.Channels
}
return 2
}
func (s *DJRandomSong) SampleRate() float64 {
if s.audiofile != nil {
return float64(s.audiofile.SampleRate)
}
return 44100
}
func (s *DJRandomSong) URL() string {
return fmt.Sprintf("djrandom://%s", s.Id.String())
}
func (s *DJRandomSong) Info() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "file: %s\n", s.URL())
fmt.Fprintf(&buf, "Last-Modified: 2010-12-16T18:02:14Z\n")
if s.Meta.Artist != "" {
fmt.Fprintf(&buf, "Artist: %s\n", s.Meta.Artist)
}
if s.Meta.Title != "" {
fmt.Fprintf(&buf, "Title: %s\n", s.Meta.Title)
}
if s.Meta.Album != "" {
fmt.Fprintf(&buf, "Album: %s\n", s.Meta.Album)
}
if s.Meta.Genre != "" {
fmt.Fprintf(&buf, "Genre: %s\n", s.Meta.Genre)
}
if s.Meta.Year > 0 {
fmt.Fprintf(&buf, "Date: %d\n", s.Meta.Year)
}
if s.Meta.TrackNum > 0 {
fmt.Fprintf(&buf, "Track: %d\n", s.Meta.TrackNum)
}
if s.audiofile != nil {
fmt.Fprintf(&buf, "Time: %d\n", int(s.audiofile.Duration))
}
return buf.String()
}
func (s *DJRandomSong) Open() (io.ReadCloser, error) {
if s.audiofile == nil {
return nil, errors.New("no audio file")
}
resp, err := s.client.GetRaw("/dl/" + s.audiofile.MD5)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
resp.Body.Close()
return nil, fmt.Errorf("HTTP error %d", resp.StatusCode)
}
return resp.Body, nil
}
type DJRandomDatabase struct {
client *util.HttpClient
}
func NewDJRandomDatabase() *DJRandomDatabase {
djrandomOnce.Do(func() {
if err := config.Parse(*configFile); err != nil {
log.Printf("Warning: could not read DJRandom config file: %v", err)
}
})
authKey := &api.AuthKey{
KeyId: *authKeyId,
Secret: *authKeySecret,
}
client := util.NewHttpClient(*serverUrl, authKey, client.LoadCA())
return &DJRandomDatabase{
client: client,
}
}
func (d *DJRandomDatabase) doQuery(query string) ([]mpd.Song, error) {
log.Printf("literal query: %s", query)
values := url.Values{}
values.Add("q", query)
req, err := d.client.NewRequest("POST", "/api/search", strings.NewReader(values.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
var resp api.SearchResponse
if err := d.client.DoJSON(nil, req, &resp); err != nil {
return nil, err
}
// Convert []api.Song to []Song.
var out []mpd.Song
for _, s := range resp.Results {
out = append(out, newDJRandomSong(s, d.client))
}
return out, nil
}
func buildQuery(args []string, exact bool) string {
var qprefix string
if exact {
qprefix = "="
}
i := 0
var qparts []string
for i < len(args) {
i++
if i >= len(args) {
break
}
what := strings.ToLower(args[i-1])
q := args[i]
if what == "any" {
qparts = append(qparts, fmt.Sprintf("\"%s%s\"", qprefix, q))
} else {
qparts = append(qparts, fmt.Sprintf("%s:\"%s%s\"", what, qprefix, q))
}
i++
}
return strings.Join(qparts, " ")
}
func (d *DJRandomDatabase) Search(args []string) ([]mpd.Song, error) {
return d.doQuery(buildQuery(args, false))
}
func (d *DJRandomDatabase) Find(args []string) ([]mpd.Song, error) {
return d.doQuery(buildQuery(args, true))
}
func (d *DJRandomDatabase) GetSong(songURL *url.URL) (mpd.Song, error) {
songID := songURL.Host
if songID == "" {
return nil, errors.New("empty song ID")
}
req, err := d.client.NewRequest("GET", "/api/song/"+songID, nil)
if err != nil {
return nil, err
}
var resp api.Song
if err := d.client.DoJSON(nil, req, &resp); err != nil {
return nil, err
}
// Convert api.Song to Song.
return newDJRandomSong(&resp, d.client), nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment