symlink support
This commit is contained in:
parent
811a8bf5fa
commit
ada3580a85
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module git.milar.in/milarin/music-library
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.milar.in/milarin/adverr v1.1.0
|
||||||
git.milar.in/milarin/envvars/v2 v2.0.0
|
git.milar.in/milarin/envvars/v2 v2.0.0
|
||||||
git.milar.in/milarin/slices v0.0.6
|
git.milar.in/milarin/slices v0.0.6
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
|
||||||
|
git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
|
||||||
git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE=
|
git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE=
|
||||||
git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY=
|
git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY=
|
||||||
git.milar.in/milarin/slices v0.0.6 h1:AQoSarZ58WHYol9c6woWJSe8wFpPC2RC4cvIlZpfg9s=
|
git.milar.in/milarin/slices v0.0.6 h1:AQoSarZ58WHYol9c6woWJSe8wFpPC2RC4cvIlZpfg9s=
|
||||||
|
27
library.go
27
library.go
@ -4,23 +4,29 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.milar.in/milarin/adverr"
|
||||||
"git.milar.in/milarin/slices"
|
"git.milar.in/milarin/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Playlist struct {
|
type Playlist struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Songs []string `json:"songs"`
|
Songs []Song `json:"songs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAll() ([]Playlist, error) {
|
type Song struct {
|
||||||
playlistNames, err := GetPlaylists()
|
Name string `json:"name"`
|
||||||
|
File string `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllPlaylists() ([]Playlist, error) {
|
||||||
|
playlistNames, err := GetAllPlaylistNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
playlists := make([]Playlist, 0, len(playlistNames))
|
playlists := make([]Playlist, 0, len(playlistNames))
|
||||||
for _, playlistName := range playlistNames {
|
for _, playlistName := range playlistNames {
|
||||||
songs, err := GetSongs(playlistName)
|
songs, err := GetSongsByPlaylist(playlistName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -34,7 +40,7 @@ func GetAll() ([]Playlist, error) {
|
|||||||
return playlists, nil
|
return playlists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlaylists() ([]string, error) {
|
func GetAllPlaylistNames() ([]string, error) {
|
||||||
entries, err := os.ReadDir(LibraryPath)
|
entries, err := os.ReadDir(LibraryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -44,12 +50,17 @@ func GetPlaylists() ([]string, error) {
|
|||||||
return slices.Map(directories, FsEntry2Name), nil
|
return slices.Map(directories, FsEntry2Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSongs(playlist string) ([]string, error) {
|
func GetSongsByPlaylist(playlist string) ([]Song, error) {
|
||||||
entries, err := os.ReadDir(filepath.Join(LibraryPath, playlist))
|
entries, err := os.ReadDir(filepath.Join(LibraryPath, playlist))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
directories := slices.Filter(entries, Not(IsDir))
|
symlinks := slices.Filter(entries, IsSymlink)
|
||||||
return slices.Map(directories, FsEntry2Name), nil
|
return slices.Map(symlinks, func(entry os.DirEntry) Song {
|
||||||
|
return Song{
|
||||||
|
Name: FsEntry2Name(entry),
|
||||||
|
File: filepath.Base(adverr.Must(os.Readlink(filepath.Join(LibraryPath, playlist, entry.Name())))),
|
||||||
|
}
|
||||||
|
}), nil
|
||||||
}
|
}
|
||||||
|
84
main.go
84
main.go
@ -6,10 +6,14 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"git.milar.in/milarin/envvars/v2"
|
"git.milar.in/milarin/envvars/v2"
|
||||||
|
"git.milar.in/milarin/slices"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,13 +24,19 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||||
|
panic("ffmpeg not found in PATH")
|
||||||
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
|
||||||
r.HandleFunc("/all/", GetAllHandler).Methods("GET")
|
r.HandleFunc("/all/", GetAllHandler).Methods("GET")
|
||||||
r.HandleFunc("/all/hash/", GetAllHashHandler).Methods("GET")
|
r.HandleFunc("/all/hash/", GetAllHashHandler).Methods("GET")
|
||||||
r.HandleFunc("/playlist/", GetPlaylistsHandler).Methods("GET")
|
r.HandleFunc("/playlist-names/", GetPlaylistNamesHandler).Methods("GET")
|
||||||
r.HandleFunc("/playlist/{playlist}/", GetPlaylistHandler).Methods("GET")
|
r.HandleFunc("/playlist/{playlist}/", GetPlaylistHandler).Methods("GET")
|
||||||
r.HandleFunc("/playlist/{playlist}/song/{song}/", GetSongHandler).Methods("GET")
|
r.HandleFunc("/file/{file}/", GetFileHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/file/{file}/{format}/", GetEncodeFileHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/file/", GetAllFilesHandler).Methods("GET")
|
||||||
|
|
||||||
fmt.Printf("Starting music server on port %d\n", HttpPort)
|
fmt.Printf("Starting music server on port %d\n", HttpPort)
|
||||||
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", HttpIntf, HttpPort), r); err != nil {
|
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", HttpIntf, HttpPort), r); err != nil {
|
||||||
@ -35,7 +45,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetAllHashHandler(w http.ResponseWriter, r *http.Request) {
|
func GetAllHashHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
playlists, err := GetAll()
|
playlists, err := GetAllPlaylists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalServerError(w, err)
|
InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
@ -55,7 +65,7 @@ func GetAllHashHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetAllHandler(w http.ResponseWriter, r *http.Request) {
|
func GetAllHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
playlists, err := GetAll()
|
playlists, err := GetAllPlaylists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalServerError(w, err)
|
InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
@ -68,40 +78,86 @@ func GetAllHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSongHandler(w http.ResponseWriter, r *http.Request) {
|
func GetFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
playlist := vars["playlist"]
|
file := vars["file"]
|
||||||
song := vars["song"]
|
|
||||||
|
|
||||||
http.ServeFile(w, r, filepath.Join(LibraryPath, playlist, song))
|
http.ServeFile(w, r, filepath.Join(LibraryPath, ".songs", file))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlaylistHandler(w http.ResponseWriter, r *http.Request) {
|
func GetPlaylistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
playlist := mux.Vars(r)["playlist"]
|
playlistName := mux.Vars(r)["playlist"]
|
||||||
|
|
||||||
songs, err := GetSongs(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 {
|
if err != nil {
|
||||||
InternalServerError(w, err)
|
InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(songs); err != nil {
|
if err := json.NewEncoder(w).Encode(playlistNames); err != nil {
|
||||||
InternalServerError(w, err)
|
InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlaylistsHandler(w http.ResponseWriter, r *http.Request) {
|
func GetAllFilesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
playlists, err := GetPlaylists()
|
fileDirectory := filepath.Join(LibraryPath, ".songs")
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(fileDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalServerError(w, err)
|
InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
files := slices.Filter(entries, IsRegular)
|
||||||
|
fileNames := slices.Map(files, FsEntry2Name)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(playlists); err != nil {
|
if err := json.NewEncoder(w).Encode(fileNames); err != nil {
|
||||||
InternalServerError(w, err)
|
InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetEncodeFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fileName := mux.Vars(r)["file"]
|
||||||
|
format := mux.Vars(r)["format"]
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
8
utils.go
8
utils.go
@ -37,6 +37,14 @@ func Not[T any](f func(T) bool) func(T) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsSymlink(entry os.DirEntry) bool {
|
||||||
|
return entry.Type()&os.ModeSymlink == os.ModeSymlink
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRegular(entry os.DirEntry) bool {
|
||||||
|
return entry.Type().IsRegular()
|
||||||
|
}
|
||||||
|
|
||||||
func IsDir(entry os.DirEntry) bool {
|
func IsDir(entry os.DirEntry) bool {
|
||||||
return entry.IsDir()
|
return entry.IsDir()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user