package main import ( "bytes" "crypto/sha512" "encoding/json" "fmt" "io" "log" "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") log.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) log.Printf("encode file to %s: '%s' -> '%s'\n", format, inputFilePath, outputFilePath) if err := EncodeVideo2Mp3(inputFilePath, outputFilePath); err != nil { InternalServerError(w, err) return } http.ServeFile(w, r, outputFilePath) }