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