package main import ( "bytes" "crypto/sha512" "encoding/hex" "encoding/json" "fmt" "io" "math/rand" "net/http" "net/url" "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 } hash := sha512.Sum512(b.Bytes()) if _, err := fmt.Fprint(w, hex.EncodeToString(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 := url.QueryUnescape(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 := url.QueryUnescape(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 } if _, err := fmt.Fprint(w, hex.EncodeToString(hasher.Sum(nil))); err != nil { InternalServerError(w, err) return } } func GetPlaylistHandler(w http.ResponseWriter, r *http.Request) { playlistName := mux.Vars(r)["playlist"] 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 := url.QueryUnescape(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) }