Migrate HTTP handlers to Echo
This commit is contained in:
parent
658ffa4344
commit
53bd2681af
|
@ -1,191 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.netflux.io/rob/clipper/media"
|
||||
"git.netflux.io/rob/clipper/youtube"
|
||||
|
||||
youtubev2 "github.com/kkdai/youtube/v2"
|
||||
"git.netflux.io/rob/clipper/server"
|
||||
)
|
||||
|
||||
const (
|
||||
ContentTypeApplicationJSON = "application/json"
|
||||
DefaultHTTPBindAddr = "0.0.0.0:8888"
|
||||
DefaultTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.Write([]byte("method not allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", ContentTypeApplicationJSON)
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/media_sets") {
|
||||
videoID := r.URL.Query().Get("video_id")
|
||||
if videoID == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"error": "no video ID provided"}`))
|
||||
return
|
||||
}
|
||||
|
||||
mediaSet := new(media.MediaSet)
|
||||
mediaSet.ID = videoID
|
||||
|
||||
if mediaSet.Exists() {
|
||||
// just load the metadata.json file:
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not fetch media"}`))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// download everything from YouTube:
|
||||
var err error
|
||||
var youtubeClient youtubev2.Client
|
||||
downloader := youtube.NewDownloader(&youtubeClient)
|
||||
log.Printf("background context = %p, req context = %p", context.Background(), r.Context())
|
||||
mediaSet, err = downloader.Download(r.Context(), videoID)
|
||||
if err != nil {
|
||||
log.Printf("error downloading MediaSet: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not fetch media"}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(mediaSet); err != nil {
|
||||
log.Printf("error encoding MediaSet: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/audio") {
|
||||
log.Printf("got headers for audio request: %+v", r.Header)
|
||||
videoID := r.URL.Query().Get("video_id")
|
||||
mediaSet := media.MediaSet{ID: videoID}
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not fetch media"}`))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: ensure content-type matches the actual downloaded format.
|
||||
w.Header().Set("Content-Type", "audio/webm")
|
||||
http.ServeFile(w, r, mediaSet.EncodedAudioPath())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/video") {
|
||||
videoID := r.URL.Query().Get("video_id")
|
||||
mediaSet := media.MediaSet{ID: videoID}
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not fetch media"}`))
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, mediaSet.VideoPath())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/thumbnails") {
|
||||
videoID := r.URL.Query().Get("video_id")
|
||||
mediaSet := media.MediaSet{ID: videoID}
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not fetch media"}`))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "image/jpeg")
|
||||
http.ServeFile(w, r, mediaSet.ThumbnailPath())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/api/peaks") {
|
||||
videoID := r.URL.Query().Get("video_id")
|
||||
if videoID == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"error": "no video ID provided"}`))
|
||||
return
|
||||
}
|
||||
|
||||
start, err := strconv.ParseInt(r.URL.Query().Get("start"), 0, 64)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"error": "invalid start parameter provided"}`))
|
||||
return
|
||||
}
|
||||
end, err := strconv.ParseInt(r.URL.Query().Get("end"), 0, 64)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"error": "invalid end parameter provided"}`))
|
||||
return
|
||||
}
|
||||
numBins, err := strconv.Atoi(r.URL.Query().Get("bins"))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"error": "invalid bins parameter provided"}`))
|
||||
return
|
||||
}
|
||||
|
||||
mediaSet := media.MediaSet{ID: videoID}
|
||||
if err = mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not fetch media"}`))
|
||||
return
|
||||
}
|
||||
|
||||
peaks, err := mediaSet.Peaks(start, end, numBins)
|
||||
if err != nil {
|
||||
log.Printf("error generating peaks: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(`{"error": "could not generate peaks"}`))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
err = json.NewEncoder(w).Encode(peaks)
|
||||
if err != nil {
|
||||
log.Printf("error encoding peaks: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("page not found"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
srv := http.Server{
|
||||
ReadTimeout: DefaultTimeout,
|
||||
WriteTimeout: DefaultTimeout,
|
||||
Addr: DefaultHTTPBindAddr,
|
||||
Handler: http.HandlerFunc(handleRequest),
|
||||
serverOptions := server.Options{
|
||||
BindAddr: DefaultHTTPBindAddr,
|
||||
Timeout: DefaultTimeout,
|
||||
}
|
||||
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
log.Fatal(server.Start(serverOptions))
|
||||
}
|
||||
|
|
|
@ -4,13 +4,24 @@ go 1.17
|
|||
|
||||
require (
|
||||
github.com/kkdai/youtube/v2 v2.7.4
|
||||
github.com/labstack/echo/v4 v4.6.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/labstack/gommon v0.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
|
|
@ -101,6 +101,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
|||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -212,10 +214,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo/v4 v4.6.0 h1:vsYEeeYy077cB5yMpgI+ubA7iVRZEtrzHhcvRhd27gA=
|
||||
github.com/labstack/echo/v4 v4.6.0/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k=
|
||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
|
@ -290,6 +304,11 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -322,6 +341,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -395,8 +416,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -427,6 +449,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -434,11 +457,13 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -466,7 +491,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg=
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -476,9 +505,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
@ -32,13 +32,16 @@ type Video struct {
|
|||
type MediaSet struct {
|
||||
Audio Audio `json:"audio"`
|
||||
Video Video `json:"video"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Source string `json:"source"`
|
||||
|
||||
exists bool
|
||||
}
|
||||
|
||||
// New builds a new MediaSet with the given ID.
|
||||
func NewMediaSet(id string) *MediaSet {
|
||||
return &MediaSet{ID: id}
|
||||
}
|
||||
|
||||
// TODO: pass io.Readers/Writers instead of strings.
|
||||
func (m *MediaSet) RawAudioPath() string { return fmt.Sprintf("cache/%s.raw", m.ID) }
|
||||
func (m *MediaSet) EncodedAudioPath() string { return fmt.Sprintf("cache/%s.m4a", m.ID) }
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.netflux.io/rob/clipper/media"
|
||||
"git.netflux.io/rob/clipper/youtube"
|
||||
youtubev2 "github.com/kkdai/youtube/v2"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// getMediaSet is a handler that responds with a MediaSet.
|
||||
func getMediaSet(c echo.Context) error {
|
||||
videoID := c.Param("id")
|
||||
mediaSet := media.NewMediaSet(videoID)
|
||||
|
||||
if mediaSet.Exists() {
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "could not fetch media set")
|
||||
}
|
||||
return c.JSON(http.StatusOK, mediaSet)
|
||||
}
|
||||
|
||||
var youtubeClient youtubev2.Client
|
||||
downloader := youtube.NewDownloader(&youtubeClient)
|
||||
mediaSet, err := downloader.Download(c.Request().Context(), videoID)
|
||||
if err != nil {
|
||||
log.Printf("error downloading MediaSet: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "could not fetch media set")
|
||||
}
|
||||
return c.JSON(http.StatusOK, mediaSet)
|
||||
}
|
||||
|
||||
// getThumbnails is a handler that responds with a MediaSet thumbnail grid.
|
||||
func getThumbnails(c echo.Context) error {
|
||||
videoID := c.Param("id")
|
||||
mediaSet := media.NewMediaSet(videoID)
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "could not load media set")
|
||||
}
|
||||
|
||||
return c.File(mediaSet.ThumbnailPath())
|
||||
}
|
||||
|
||||
// getVideo is a handler that responds with the video file for a MediaSet
|
||||
func getVideo(c echo.Context) error {
|
||||
videoID := c.Param("id")
|
||||
mediaSet := media.NewMediaSet(videoID)
|
||||
if err := mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "could not load media set")
|
||||
}
|
||||
|
||||
return c.File(mediaSet.VideoPath())
|
||||
}
|
||||
|
||||
// getPeaks is a handler that returns a two-dimensional array of peaks, with
|
||||
// the number of bins matching the provided parameter.
|
||||
func getPeaks(c echo.Context) error {
|
||||
videoID := c.Param("id")
|
||||
|
||||
start, err := strconv.ParseInt(c.QueryParam("start"), 0, 64)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid start parameter provided")
|
||||
}
|
||||
|
||||
end, err := strconv.ParseInt(c.QueryParam("end"), 0, 64)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid end parameter provided")
|
||||
}
|
||||
|
||||
numBins, err := strconv.Atoi(c.QueryParam("bins"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid bins parameter provided")
|
||||
}
|
||||
|
||||
mediaSet := media.NewMediaSet(videoID)
|
||||
if err = mediaSet.Load(); err != nil {
|
||||
log.Printf("error loading MediaSet: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "could not load media set")
|
||||
}
|
||||
|
||||
peaks, err := mediaSet.Peaks(start, end, numBins)
|
||||
if err != nil {
|
||||
log.Printf("error generating peaks: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "could not generate peaks")
|
||||
}
|
||||
|
||||
return json.NewEncoder(c.Response()).Encode(peaks)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
BindAddr string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func Start(opts Options) error {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{Timeout: opts.Timeout}))
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"http://localhost:3000"},
|
||||
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
|
||||
}))
|
||||
|
||||
e.GET("/api/media_sets/:id", getMediaSet)
|
||||
e.GET("/api/media_sets/:id/thumbnails", getThumbnails)
|
||||
e.GET("/api/media_sets/:id/video", getVideo)
|
||||
e.GET("/api/media_sets/:id/peaks", getPeaks)
|
||||
|
||||
return e.Start(opts.BindAddr)
|
||||
}
|
|
@ -71,7 +71,7 @@ func (d *Downloader) Download(ctx context.Context, videoID string) (*media.Media
|
|||
return nil, fmt.Errorf("error fetching video: %v", err)
|
||||
}
|
||||
|
||||
mediaSet := media.MediaSet{ID: videoID, Source: "youtube"}
|
||||
mediaSet := media.NewMediaSet(videoID)
|
||||
|
||||
audioResultChan := make(chan audioResult, 1)
|
||||
videoResultChan := make(chan videoResult, 1)
|
||||
|
@ -123,7 +123,7 @@ func (d *Downloader) Download(ctx context.Context, videoID string) (*media.Media
|
|||
|
||||
log.Println("finished downloading mediaset")
|
||||
|
||||
return &mediaSet, nil
|
||||
return mediaSet, nil
|
||||
}
|
||||
|
||||
func (d *Downloader) downloadAudio(ctx context.Context, video *youtubev2.Video, outPath, rawOutPath string) (*media.Audio, error) {
|
||||
|
|
|
@ -87,7 +87,7 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
|||
console.log('fetching media...');
|
||||
|
||||
const resp = await fetch(
|
||||
`http://localhost:8888/api/media_sets?video_id=${videoID}`
|
||||
`http://localhost:8888/api/media_sets/${videoID}`
|
||||
);
|
||||
const respBody = await resp.json();
|
||||
|
||||
|
@ -129,7 +129,7 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const url = `http://localhost:8888/api/video?video_id=${videoID}`;
|
||||
const url = `http://localhost:8888/api/media_sets/${videoID}/video`;
|
||||
video.src = url;
|
||||
video.muted = false;
|
||||
video.volume = 1;
|
||||
|
@ -149,7 +149,7 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
|||
}
|
||||
|
||||
const resp = await fetch(
|
||||
`http://localhost:8888/api/peaks?video_id=${videoID}&start=${zoomSettings.startFrame}&end=${endFrame}&bins=${CanvasLogicalWidth}`
|
||||
`http://localhost:8888/api/media_sets/${videoID}/peaks?start=${zoomSettings.startFrame}&end=${endFrame}&bins=${CanvasLogicalWidth}`
|
||||
);
|
||||
const peaks = await resp.json();
|
||||
setWaveformPeaks(peaks);
|
||||
|
|
|
@ -21,7 +21,7 @@ export const Thumbnails: React.FC<Props> = ({ mediaSet, style }: Props) => {
|
|||
useEffect(() => {
|
||||
if (mediaSet == null) return;
|
||||
|
||||
image.src = `http://localhost:8888/api/thumbnails?video_id=${mediaSet.id}`;
|
||||
image.src = `http://localhost:8888/api/media_sets/${mediaSet.id}/thumbnails`;
|
||||
image.onload = () => {
|
||||
setState(State.Ready);
|
||||
};
|
||||
|
@ -47,7 +47,6 @@ export const Thumbnails: React.FC<Props> = ({ mediaSet, style }: Props) => {
|
|||
const tw = mediaSet.video.thumbnailWidth;
|
||||
const th = mediaSet.video.thumbnailHeight;
|
||||
const iw = image.width;
|
||||
const ih = image.height;
|
||||
const { width: pw, height: ph } = canvas.getBoundingClientRect();
|
||||
|
||||
// set canvas logical width to suit the aspect ratio:
|
||||
|
|
Loading…
Reference in New Issue