diff options
| author | Physick <96335032+DegustatorPonos@users.noreply.github.com> | 2025-12-06 12:05:43 +0500 |
|---|---|---|
| committer | Physick <96335032+DegustatorPonos@users.noreply.github.com> | 2025-12-06 12:05:43 +0500 |
| commit | 5fd81183f54662fdf08549c0d635e80166c62fe9 (patch) | |
| tree | 0b730dd4f19c7446fabcc2ee7cc7313e94e67fe3 | |
initial endpoints
| -rw-r--r-- | Audio/Meta.go | 35 | ||||
| -rw-r--r-- | Endpoints/Audio.go | 46 | ||||
| -rw-r--r-- | Endpoints/Common.go | 8 | ||||
| -rw-r--r-- | Endpoints/SongList.go | 56 | ||||
| -rw-r--r-- | Settings.json | 3 | ||||
| -rw-r--r-- | Settings/Settings.go | 66 | ||||
| -rw-r--r-- | go.mod | 5 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | main.go | 19 | ||||
| -rw-r--r-- | static/index.html | 9 | ||||
| -rw-r--r-- | static/index.js | 5 |
11 files changed, 254 insertions, 0 deletions
diff --git a/Audio/Meta.go b/Audio/Meta.go new file mode 100644 index 0000000..9e288f3 --- /dev/null +++ b/Audio/Meta.go @@ -0,0 +1,35 @@ +package audio + +import ( + "io" + "os" + "time" + + "github.com/tcolgate/mp3" +) + +func GetSongDuration(path string) (time.Duration, error) { + var file, fopenErr = os.Open(path) + if fopenErr != nil { + return time.Duration(0), fopenErr + } + defer file.Close() + + var track = mp3.NewDecoder(file) + var duration time.Duration + var frame mp3.Frame + var skipped = 0 + + for { + var err = track.Decode(&frame, &skipped) + if err != nil { + if err == io.EOF { + break + } + return time.Duration(0), err + } + + duration += frame.Duration() + } + return duration, nil +} diff --git a/Endpoints/Audio.go b/Endpoints/Audio.go new file mode 100644 index 0000000..f9dca20 --- /dev/null +++ b/Endpoints/Audio.go @@ -0,0 +1,46 @@ +package endpoints + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + settings "physick.ru/culture_exam/Settings" +) + +type getErr struct { + Error string +} + +func (base getErr) String() string { + var outp, jsonErr = json.Marshal(base) + if jsonErr != nil { + return fmt.Sprintf("Failed to parse settings: %s", jsonErr.Error()) + } + return string(outp) +} + +func getSong(w http.ResponseWriter, r *http.Request) { + var songName = r.URL.Query().Get("name") + if len(songName) == 0 { + fmt.Fprint(w, getErr { Error: "No song name provided" }) + w.WriteHeader(http.StatusBadRequest) + return + } + var fullPath = fmt.Sprintf("%s/%s", settings.Current.SongsLocation, songName) + var file, fopenErr = os.ReadFile(fullPath) + if fopenErr != nil { + fmt.Fprint(w, getErr { Error: fopenErr.Error()}) + w.WriteHeader(http.StatusBadRequest) + return + } + w.Header().Add("Content-Type", "audio/mpeg") + var audioChunks = bytes.NewBuffer(file) + + if _, err := audioChunks.WriteTo(w); err != nil { + log.Println(err) + } +} diff --git a/Endpoints/Common.go b/Endpoints/Common.go new file mode 100644 index 0000000..b30afd0 --- /dev/null +++ b/Endpoints/Common.go @@ -0,0 +1,8 @@ +package endpoints + +import "net/http" + +func RegisterEndpoints() { + http.HandleFunc("/api/getSongs", getSongs) + http.HandleFunc("/api/getSong", getSong) +} diff --git a/Endpoints/SongList.go b/Endpoints/SongList.go new file mode 100644 index 0000000..9830c74 --- /dev/null +++ b/Endpoints/SongList.go @@ -0,0 +1,56 @@ +package endpoints + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + audio "physick.ru/culture_exam/Audio" + settings "physick.ru/culture_exam/Settings" +) + +type Songs = struct { + Name string + Duration int +} + +type SongsList struct { + Songs []Songs +} + +func (base SongsList) String() string { + var outp, jsonErr = json.Marshal(base) + if jsonErr != nil { + return fmt.Sprintf("Failed to parse settings: %s", jsonErr.Error()) + } + return string(outp) +} + +func getSongs(w http.ResponseWriter, r *http.Request) { + var files, dirErr = os.ReadDir(settings.Current.SongsLocation) + if dirErr != nil { + log.Printf("Failed to look up songs list: %s", dirErr.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + var outp = SongsList { + Songs: make([]Songs, 0, len(files)), + } + for _, v := range files { + var fullPath = fmt.Sprintf("%s/%s", settings.Current.SongsLocation, v.Name()) + var duration, durErr = audio.GetSongDuration(fullPath) + if durErr != nil { + log.Printf("Failed to parse file metadata: File: %s, error: %s\n", fullPath, durErr.Error()) + continue + } + var song = Songs { + Name: v.Name(), + Duration: int(duration.Seconds()), + } + outp.Songs = append(outp.Songs, song) + } + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, outp) +} diff --git a/Settings.json b/Settings.json new file mode 100644 index 0000000..9f14747 --- /dev/null +++ b/Settings.json @@ -0,0 +1,3 @@ +{ + "SongsLocation": "/home/physick/Music/Architects" +} diff --git a/Settings/Settings.go b/Settings/Settings.go new file mode 100644 index 0000000..e859677 --- /dev/null +++ b/Settings/Settings.go @@ -0,0 +1,66 @@ +package settings + +import ( + "encoding/json" + "fmt" + "os" +) + +const settingsLocation = "Settings.json" + +type Settings struct { + SongsLocation string +} + +var defaultSettings = Settings { + SongsLocation: "./Songs", +} + +func (base Settings) String() string { + var outp, jsonErr = json.Marshal(base) + if jsonErr != nil { + return fmt.Sprintf("Failed to parse settings: %s", jsonErr.Error()) + } + return string(outp) +} + +var Current Settings = Settings{} + +func ReadSettings() { + var file, fopenerr = os.ReadFile(settingsLocation) + if fopenerr != nil { + var success, inintBody = createSettingsFile() + if success { + file = inintBody + } else { + panic(fmt.Sprintf("Unable to load or create settings file. \nOriginal error: %v\n", fopenerr.Error())) + } + } + var data = Settings{} + json.Unmarshal(file, &data) + var validationErr = data.Validate() + if validationErr != nil { + panic(fmt.Sprintf("Failed to load settings file: %s", validationErr.Error())) + } + Current = data +} + +func createSettingsFile() (bool, []byte ) { + var file, err = os.Create(settingsLocation) + if err != nil { + return false, nil + } + defer file.Close() + var inititalData = defaultSettings + var body, jsonerr = json.MarshalIndent(inititalData, "", " ") + if jsonerr != nil { + return false, nil + } + fmt.Fprint(file, string(body)) + return true, body +} + +// Returns an error with the issue description if the settings are invalid +func (base *Settings) Validate() error { + return nil +} @@ -0,0 +1,5 @@ +module physick.ru/culture_exam + +go 1.25.4 + +require github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300 // indirect @@ -0,0 +1,2 @@ +github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300 h1:XQdibLKagjdevRB6vAjVY4qbSr8rQ610YzTkWcxzxSI= +github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300/go.mod h1:FNa/dfN95vAYCNFrIKRrlRo+MBLbwmR9Asa5f2ljmBI= @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "net/http" + "os" + + endpoints "physick.ru/culture_exam/Endpoints" + settings "physick.ru/culture_exam/Settings" +) + +func main() { + settings.ReadSettings() + fmt.Println(settings.Current) + endpoints.RegisterEndpoints() + http.Handle("/", http.FileServer(http.FS(os.DirFS("./static")))) + + http.ListenAndServe(":6969", nil) +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..8f1caf6 --- /dev/null +++ b/static/index.html @@ -0,0 +1,9 @@ +<html> + <head> + <script src="./index.js"></script> + </head> + <body> + <h1> xdx </h1> + <button onclick="GetSongs()"> Get songs </button> + </body> +</html> diff --git a/static/index.js b/static/index.js new file mode 100644 index 0000000..9f5070b --- /dev/null +++ b/static/index.js @@ -0,0 +1,5 @@ +function GetSongs() { + fetch("/api/getSongs").then(r => r.json().then(body => { + console.log(body); + })); +} |
