music-library/main.go

208 lines
4.9 KiB
Go

package main
import (
"bytes"
"crypto/sha512"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"git.milar.in/milarin/envvars/v2"
"git.milar.in/milarin/slices"
"github.com/gorilla/mux"
)
var (
HttpIntf = envvars.String("HTTP_INTERFACE", "")
HttpPort = envvars.Uint16("HTTP_PORT", 80)
LibraryPath = envvars.String("LIBRARY_PATH", "/music")
)
func main() {
if _, err := exec.LookPath("ffmpeg"); err != nil {
panic("ffmpeg not found in PATH")
}
r := mux.NewRouter()
r.HandleFunc("/all/", GetAllHandler).Methods("GET")
r.HandleFunc("/all/hash/", GetAllHashHandler).Methods("GET")
r.HandleFunc("/playlist-names/", GetPlaylistNamesHandler).Methods("GET")
r.HandleFunc("/playlist/{playlist}/", GetPlaylistHandler).Methods("GET")
r.HandleFunc("/file/{file}/", GetFileHandler).Methods("GET")
r.HandleFunc("/file/{file}/hash/", GetFileHashHandler).Methods("GET")
r.HandleFunc("/file/{file}/format/{format}/", GetEncodeFileHandler).Methods("GET")
r.HandleFunc("/file/", GetAllFilesHandler).Methods("GET")
fmt.Printf("Starting music server on port %d\n", HttpPort)
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", HttpIntf, HttpPort), r); err != nil {
panic(err)
}
}
func GetAllHashHandler(w http.ResponseWriter, r *http.Request) {
playlists, err := GetAllPlaylists()
if err != nil {
InternalServerError(w, err)
return
}
b := &bytes.Buffer{}
if err := json.NewEncoder(b).Encode(playlists); err != nil {
InternalServerError(w, err)
return
}
w.Header().Add("Content-Type", "application/json")
hash := sha512.Sum512(b.Bytes())
if err := json.NewEncoder(w).Encode(NewHash(hash[:])); err != nil {
InternalServerError(w, err)
return
}
}
func GetAllHandler(w http.ResponseWriter, r *http.Request) {
playlists, err := GetAllPlaylists()
if err != nil {
InternalServerError(w, err)
return
}
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(playlists); err != nil {
InternalServerError(w, err)
return
}
}
func GetFileHandler(w http.ResponseWriter, r *http.Request) {
fileName, err := DecodeBase64String(mux.Vars(r)["file"])
if err != nil {
InternalServerError(w, err)
return
}
http.ServeFile(w, r, filepath.Join(LibraryPath, ".songs", fileName))
}
func GetFileHashHandler(w http.ResponseWriter, r *http.Request) {
fileName, err := DecodeBase64String(mux.Vars(r)["file"])
if err != nil {
InternalServerError(w, err)
return
}
filePath := filepath.Join(LibraryPath, ".songs", fileName)
f, err := os.Open(filePath)
if err != nil {
InternalServerError(w, err)
return
}
defer f.Close()
hasher := sha512.New()
if _, err := io.Copy(hasher, f); err != nil {
InternalServerError(w, err)
return
}
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(NewHash(hasher.Sum(nil))); err != nil {
InternalServerError(w, err)
return
}
}
func GetPlaylistHandler(w http.ResponseWriter, r *http.Request) {
playlistName, err := DecodeBase64String(mux.Vars(r)["playlist"])
if err != nil {
InternalServerError(w, err)
return
}
songs, err := GetSongsByPlaylist(playlistName)
if err != nil {
InternalServerError(w, err)
return
}
playlist := Playlist{
Name: playlistName,
Songs: songs,
}
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(playlist); err != nil {
InternalServerError(w, err)
return
}
}
func GetPlaylistNamesHandler(w http.ResponseWriter, r *http.Request) {
playlistNames, err := GetAllPlaylistNames()
if err != nil {
InternalServerError(w, err)
return
}
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(playlistNames); err != nil {
InternalServerError(w, err)
return
}
}
func GetAllFilesHandler(w http.ResponseWriter, r *http.Request) {
fileDirectory := filepath.Join(LibraryPath, ".songs")
entries, err := os.ReadDir(fileDirectory)
if err != nil {
InternalServerError(w, err)
return
}
files := slices.Filter(entries, IsRegular)
fileNames := slices.Map(files, FsEntry2Name)
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(fileNames); err != nil {
InternalServerError(w, err)
return
}
}
func GetEncodeFileHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
format := vars["format"]
fileName, err := DecodeBase64String(vars["file"])
if err != nil {
InternalServerError(w, err)
return
}
inputFilePath := filepath.Join(LibraryPath, ".songs", fileName)
outputFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%d.%s", rand.Int(), format))
defer os.Remove(outputFilePath)
fmt.Printf("encode file to %s: '%s' -> '%s'\n", format, inputFilePath, outputFilePath)
cmd := exec.Command("ffmpeg", "-i", inputFilePath, "-vn", outputFilePath)
if err := cmd.Start(); err != nil {
InternalServerError(w, err)
return
}
if err := cmd.Wait(); err != nil {
InternalServerError(w, err)
return
}
http.ServeFile(w, r, outputFilePath)
}