2023-02-14 21:59:56 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-02-16 14:27:18 +01:00
|
|
|
"io"
|
2023-02-25 12:13:04 +01:00
|
|
|
"log"
|
2023-02-16 12:16:04 +01:00
|
|
|
"math/rand"
|
2023-02-14 21:59:56 +01:00
|
|
|
"net/http"
|
2023-02-16 12:16:04 +01:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2023-02-14 21:59:56 +01:00
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"git.milar.in/milarin/envvars/v2"
|
2023-02-16 12:16:04 +01:00
|
|
|
"git.milar.in/milarin/slices"
|
2023-02-14 21:59:56 +01:00
|
|
|
"github.com/gorilla/mux"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
HttpIntf = envvars.String("HTTP_INTERFACE", "")
|
|
|
|
HttpPort = envvars.Uint16("HTTP_PORT", 80)
|
|
|
|
LibraryPath = envvars.String("LIBRARY_PATH", "/music")
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2023-02-16 12:16:04 +01:00
|
|
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
|
|
|
panic("ffmpeg not found in PATH")
|
|
|
|
}
|
|
|
|
|
2023-02-14 21:59:56 +01:00
|
|
|
r := mux.NewRouter()
|
|
|
|
|
|
|
|
r.HandleFunc("/all/", GetAllHandler).Methods("GET")
|
|
|
|
r.HandleFunc("/all/hash/", GetAllHashHandler).Methods("GET")
|
2023-02-16 12:16:04 +01:00
|
|
|
r.HandleFunc("/playlist-names/", GetPlaylistNamesHandler).Methods("GET")
|
2023-02-14 21:59:56 +01:00
|
|
|
r.HandleFunc("/playlist/{playlist}/", GetPlaylistHandler).Methods("GET")
|
2023-02-16 12:16:04 +01:00
|
|
|
r.HandleFunc("/file/{file}/", GetFileHandler).Methods("GET")
|
2023-02-16 14:27:18 +01:00
|
|
|
r.HandleFunc("/file/{file}/hash/", GetFileHashHandler).Methods("GET")
|
|
|
|
r.HandleFunc("/file/{file}/format/{format}/", GetEncodeFileHandler).Methods("GET")
|
2023-02-16 12:16:04 +01:00
|
|
|
r.HandleFunc("/file/", GetAllFilesHandler).Methods("GET")
|
2023-02-14 21:59:56 +01:00
|
|
|
|
2023-02-25 12:13:04 +01:00
|
|
|
log.Printf("Starting music server on port %d\n", HttpPort)
|
2023-02-14 21:59:56 +01:00
|
|
|
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", HttpIntf, HttpPort), r); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetAllHashHandler(w http.ResponseWriter, r *http.Request) {
|
2023-02-16 12:16:04 +01:00
|
|
|
playlists, err := GetAllPlaylists()
|
2023-02-14 21:59:56 +01:00
|
|
|
if err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
if err := json.NewEncoder(b).Encode(playlists); err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-16 14:36:52 +01:00
|
|
|
w.Header().Add("Content-Type", "application/json")
|
2023-02-14 21:59:56 +01:00
|
|
|
hash := sha512.Sum512(b.Bytes())
|
2023-02-16 14:36:52 +01:00
|
|
|
if err := json.NewEncoder(w).Encode(NewHash(hash[:])); err != nil {
|
2023-02-14 21:59:56 +01:00
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetAllHandler(w http.ResponseWriter, r *http.Request) {
|
2023-02-16 12:16:04 +01:00
|
|
|
playlists, err := GetAllPlaylists()
|
2023-02-14 21:59:56 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-16 12:16:04 +01:00
|
|
|
func GetFileHandler(w http.ResponseWriter, r *http.Request) {
|
2023-02-16 15:15:41 +01:00
|
|
|
fileName, err := DecodeBase64String(mux.Vars(r)["file"])
|
2023-02-16 14:09:22 +01:00
|
|
|
if err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2023-02-14 21:59:56 +01:00
|
|
|
|
2023-02-16 14:09:22 +01:00
|
|
|
http.ServeFile(w, r, filepath.Join(LibraryPath, ".songs", fileName))
|
2023-02-14 21:59:56 +01:00
|
|
|
}
|
|
|
|
|
2023-02-16 14:27:18 +01:00
|
|
|
func GetFileHashHandler(w http.ResponseWriter, r *http.Request) {
|
2023-02-16 15:15:41 +01:00
|
|
|
fileName, err := DecodeBase64String(mux.Vars(r)["file"])
|
2023-02-16 14:27:18 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-02-16 14:36:52 +01:00
|
|
|
w.Header().Add("Content-Type", "application/json")
|
|
|
|
if err := json.NewEncoder(w).Encode(NewHash(hasher.Sum(nil))); err != nil {
|
2023-02-16 14:27:18 +01:00
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-14 21:59:56 +01:00
|
|
|
func GetPlaylistHandler(w http.ResponseWriter, r *http.Request) {
|
2023-02-16 15:15:41 +01:00
|
|
|
playlistName, err := DecodeBase64String(mux.Vars(r)["playlist"])
|
|
|
|
if err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2023-02-14 21:59:56 +01:00
|
|
|
|
2023-02-16 12:16:04 +01:00
|
|
|
songs, err := GetSongsByPlaylist(playlistName)
|
2023-02-14 21:59:56 +01:00
|
|
|
if err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-16 12:16:04 +01:00
|
|
|
playlist := Playlist{
|
|
|
|
Name: playlistName,
|
|
|
|
Songs: songs,
|
|
|
|
}
|
|
|
|
|
2023-02-14 21:59:56 +01:00
|
|
|
w.Header().Add("Content-Type", "application/json")
|
2023-02-16 12:16:04 +01:00
|
|
|
if err := json.NewEncoder(w).Encode(playlist); err != nil {
|
2023-02-14 21:59:56 +01:00
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-16 12:16:04 +01:00
|
|
|
func GetPlaylistNamesHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
playlistNames, err := GetAllPlaylistNames()
|
2023-02-14 21:59:56 +01:00
|
|
|
if err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Add("Content-Type", "application/json")
|
2023-02-16 12:16:04 +01:00
|
|
|
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 {
|
2023-02-14 21:59:56 +01:00
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2023-02-16 12:16:04 +01:00
|
|
|
|
|
|
|
func GetEncodeFileHandler(w http.ResponseWriter, r *http.Request) {
|
2023-02-16 14:09:22 +01:00
|
|
|
vars := mux.Vars(r)
|
|
|
|
format := vars["format"]
|
2023-02-16 15:15:41 +01:00
|
|
|
|
|
|
|
fileName, err := DecodeBase64String(vars["file"])
|
2023-02-16 14:08:01 +01:00
|
|
|
if err != nil {
|
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2023-02-16 12:16:04 +01:00
|
|
|
|
|
|
|
inputFilePath := filepath.Join(LibraryPath, ".songs", fileName)
|
|
|
|
outputFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%d.%s", rand.Int(), format))
|
|
|
|
defer os.Remove(outputFilePath)
|
|
|
|
|
2023-02-25 12:13:04 +01:00
|
|
|
log.Printf("encode file to %s: '%s' -> '%s'\n", format, inputFilePath, outputFilePath)
|
2023-02-16 12:16:04 +01:00
|
|
|
|
2023-02-16 15:33:08 +01:00
|
|
|
if err := EncodeVideo2Mp3(inputFilePath, outputFilePath); err != nil {
|
2023-02-16 12:16:04 +01:00
|
|
|
InternalServerError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
http.ServeFile(w, r, outputFilePath)
|
|
|
|
}
|