parent
fdaef787ff
commit
a2753fa627
@ -0,0 +1,22 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"dread.land/deepgram-demo/internal/apiServer" |
||||||
|
"dread.land/deepgram-demo/internal/audio" |
||||||
|
"dread.land/deepgram-demo/internal/metadata" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
fmt.Println("Hello, World!") |
||||||
|
|
||||||
|
// would be better off as config
|
||||||
|
audioStorage := audio.NewAudioMemoryStorage() |
||||||
|
metadataStorage := metadata.NewMetadataMemoryStorage() |
||||||
|
audioServer := &audio.AudioServer{Storage: audioStorage, Metadata: metadataStorage} |
||||||
|
metadataServer := &metadata.MetadataServer{Storage: metadataStorage} |
||||||
|
apiServer := &apiServer.ApiServer{Audio: audioServer, Metadata: metadataServer} |
||||||
|
|
||||||
|
apiServer.Serve() |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
module dread.land/deepgram-demo |
||||||
|
|
||||||
|
go 1.19 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/go-audio/audio v1.0.0 // indirect |
||||||
|
github.com/go-audio/riff v1.0.0 // indirect |
||||||
|
github.com/go-audio/wav v1.1.0 |
||||||
|
) |
||||||
@ -0,0 +1,106 @@ |
|||||||
|
package apiServer |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"path" |
||||||
|
"strconv" |
||||||
|
|
||||||
|
"dread.land/deepgram-demo/internal/audio" |
||||||
|
"dread.land/deepgram-demo/internal/metadata" |
||||||
|
) |
||||||
|
|
||||||
|
type ApiServer struct { |
||||||
|
Audio *audio.AudioServer |
||||||
|
Metadata *metadata.MetadataServer |
||||||
|
} |
||||||
|
|
||||||
|
func (apiServer *ApiServer) filesHandler(w http.ResponseWriter, r *http.Request) { |
||||||
|
|
||||||
|
filename := path.Base(r.URL.Path) |
||||||
|
switch r.Method { |
||||||
|
case "GET": |
||||||
|
// get file
|
||||||
|
contents, err := apiServer.Audio.Download(filename) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusNotFound) |
||||||
|
return |
||||||
|
} |
||||||
|
w.Write(contents) |
||||||
|
return |
||||||
|
case "POST": // save file
|
||||||
|
contents, err := io.ReadAll(r.Body) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusBadRequest) |
||||||
|
} |
||||||
|
err = apiServer.Audio.Upload(filename, contents) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusConflict) |
||||||
|
} |
||||||
|
return |
||||||
|
default: |
||||||
|
// error unsupported
|
||||||
|
http.Error(w, "unsupported", http.StatusMethodNotAllowed) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (apiServer *ApiServer) metadataHandler(w http.ResponseWriter, r *http.Request) { |
||||||
|
filename := path.Base(r.URL.Path) |
||||||
|
switch r.Method { |
||||||
|
case "GET": |
||||||
|
// get metadata
|
||||||
|
meta, err := apiServer.Metadata.Info(filename) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusNotFound) |
||||||
|
return |
||||||
|
} |
||||||
|
response, err := json.Marshal(meta) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
fmt.Fprint(w, string(response)) |
||||||
|
return |
||||||
|
default: |
||||||
|
http.Error(w, "unsupported", http.StatusMethodNotAllowed) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (apiServer *ApiServer) listHandler(w http.ResponseWriter, r *http.Request) { |
||||||
|
maxdurationString := r.URL.Query().Get("maxduration") |
||||||
|
maxduration := 0 |
||||||
|
var err error |
||||||
|
if maxdurationString != "" { |
||||||
|
maxduration, err = strconv.Atoi(maxdurationString) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusBadRequest) |
||||||
|
return // error Bad Request
|
||||||
|
} |
||||||
|
list := apiServer.Metadata.List(maxduration) |
||||||
|
response, err := json.Marshal(list) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
fmt.Fprint(w, string(response)) |
||||||
|
} |
||||||
|
|
||||||
|
func (apiServer *ApiServer) Serve() { |
||||||
|
|
||||||
|
// POST and GET media
|
||||||
|
http.HandleFunc("/files/", apiServer.filesHandler) |
||||||
|
// list media
|
||||||
|
http.HandleFunc("/files", apiServer.listHandler) |
||||||
|
// list metadata (with filter)
|
||||||
|
http.HandleFunc("/metadata", apiServer.listHandler) |
||||||
|
// GET metadata
|
||||||
|
http.HandleFunc("/metadata/", apiServer.metadataHandler) |
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":3030", nil)) |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
package audio |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/go-audio/wav" |
||||||
|
|
||||||
|
"dread.land/deepgram-demo/internal/metadata" |
||||||
|
) |
||||||
|
|
||||||
|
type AudioServer struct { |
||||||
|
Storage *AudioMemoryStorage |
||||||
|
Metadata *metadata.MetadataMemoryStorage |
||||||
|
} |
||||||
|
|
||||||
|
func (a *AudioServer) Upload(filename string, contents []byte) error { |
||||||
|
fmt.Println(a) |
||||||
|
|
||||||
|
// extract duration and save as metadata
|
||||||
|
r := bytes.NewReader(contents) |
||||||
|
decoder := wav.NewDecoder(r) |
||||||
|
if !decoder.IsValidFile() { |
||||||
|
return errors.New("invalid wav file") |
||||||
|
} |
||||||
|
dur, err := decoder.Duration() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
err = a.Metadata.Put(metadata.AudioMetadata{FileName: filename, Duration: int(dur.Milliseconds())}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
err = a.Storage.put(AudioFile{FileName: filename, Contents: contents}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (a *AudioServer) Download(filename string) ([]byte, error) { |
||||||
|
file, err := a.Storage.get(filename) |
||||||
|
return file.Contents, err |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
package audio |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
) |
||||||
|
|
||||||
|
type AudioFile struct { |
||||||
|
FileName string |
||||||
|
Contents []byte |
||||||
|
} |
||||||
|
|
||||||
|
type AudioMemoryStorage struct { |
||||||
|
Data map[string]AudioFile |
||||||
|
} |
||||||
|
|
||||||
|
func NewAudioMemoryStorage() *AudioMemoryStorage { |
||||||
|
return &AudioMemoryStorage{Data: map[string]AudioFile{}} |
||||||
|
} |
||||||
|
|
||||||
|
func (audio *AudioMemoryStorage) put(a AudioFile) (err error) { |
||||||
|
_, ok := audio.Data[a.FileName] |
||||||
|
if !ok { |
||||||
|
audio.Data[a.FileName] = a |
||||||
|
return nil |
||||||
|
} |
||||||
|
return errors.New("already exists") |
||||||
|
} |
||||||
|
|
||||||
|
func (audio *AudioMemoryStorage) get(filename string) (a AudioFile, err error) { |
||||||
|
val, ok := audio.Data[filename] |
||||||
|
if ok { |
||||||
|
return val, nil |
||||||
|
} |
||||||
|
return AudioFile{}, errors.New("not found") |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
package metadata |
||||||
|
|
||||||
|
type MetadataServer struct { |
||||||
|
Storage *MetadataMemoryStorage |
||||||
|
} |
||||||
|
|
||||||
|
func (m *MetadataServer) Info(filename string) (AudioMetadata, error) { |
||||||
|
a, err := m.Storage.get(filename) |
||||||
|
if err == nil { |
||||||
|
return a, nil |
||||||
|
} |
||||||
|
return AudioMetadata{}, err |
||||||
|
} |
||||||
|
|
||||||
|
func (m *MetadataServer) List(maxduration int) []string { |
||||||
|
a := m.Storage.filterMaxDuration(maxduration) |
||||||
|
return a |
||||||
|
} |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
package metadata |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type AudioMetadata struct { |
||||||
|
FileName string `json:"filename"` |
||||||
|
Duration int `json:"duration"` |
||||||
|
} |
||||||
|
|
||||||
|
type MetadataMemoryStorage struct { |
||||||
|
Metadata map[string]AudioMetadata |
||||||
|
} |
||||||
|
|
||||||
|
func NewMetadataMemoryStorage() *MetadataMemoryStorage { |
||||||
|
return &MetadataMemoryStorage{Metadata: map[string]AudioMetadata{}} |
||||||
|
} |
||||||
|
|
||||||
|
func (metadata *MetadataMemoryStorage) Put(a AudioMetadata) (err error) { |
||||||
|
fmt.Println(metadata, a) |
||||||
|
_, ok := metadata.Metadata[a.FileName] |
||||||
|
if !ok { |
||||||
|
metadata.Metadata[a.FileName] = a |
||||||
|
return nil |
||||||
|
} |
||||||
|
return errors.New("already exists") // 409 Conflict
|
||||||
|
} |
||||||
|
|
||||||
|
func (metadata *MetadataMemoryStorage) get(filename string) (a AudioMetadata, err error) { |
||||||
|
val, ok := metadata.Metadata[filename] |
||||||
|
if ok { |
||||||
|
return val, nil |
||||||
|
} |
||||||
|
return AudioMetadata{}, errors.New("not found") // 404 Not Found
|
||||||
|
} |
||||||
|
|
||||||
|
func (metadata *MetadataMemoryStorage) filterMaxDuration(maxDuration int) (a []string) { |
||||||
|
result := []string{} |
||||||
|
for _, item := range metadata.Metadata { |
||||||
|
if (item.Duration <= maxDuration) || (maxDuration <= 0) { |
||||||
|
result = append(result, item.FileName) |
||||||
|
} |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
Loading…
Reference in new issue