all repos — fixyoutube-go @ 78a91dca196fbcf8499a86c8282cce3b83e1302d

A better way to embed YouTube videos everywhere (inspired by FixTweet).

cache video buffers
Marco Andronaco andronacomarco@gmail.com
Mon, 15 Jan 2024 15:41:21 +0100
commit

78a91dca196fbcf8499a86c8282cce3b83e1302d

parent

636c962ec2a61c90f7451b365aa808a957a9082e

5 files changed, 68 insertions(+), 29 deletions(-)

jump to
M fixyoutube.gofixyoutube.go

@@ -131,22 +131,23 @@ vars := mux.Vars(r)

videoId := vars["videoId"] b, l, s := invidiousClient.ProxyVideoId(videoId) - if l > 0 { - h := w.Header() - h.Set("Status", "200") - h.Set("Content-Type", "video/mp4") - h.Set("Content-Length", strconv.FormatInt(l, 10)) - io.Copy(w, b) + if l == 0 || l != int64(b.Len()) { + logger.Error("proxyHandler() failed. Final code: ", s) + http.Error(w, "Something went wrong.", s) return } + h := w.Header() + h.Set("Status", "200") + h.Set("Content-Type", "video/mp4") + h.Set("Content-Length", strconv.FormatInt(l, 10)) - logger.Error("proxyHandler() failed. Final code: ", s) - http.Error(w, "Something went wrong.", s) + io.Copy(w, b) + return } } func main() { - //logger.SetLevel(logrus.DebugLevel) + logger.SetLevel(logrus.DebugLevel) err := godotenv.Load() if err != nil { logger.Info("No .env file provided.")
M invidious/api.goinvidious/api.go

@@ -55,11 +55,13 @@ return nil, http.StatusInternalServerError

} res.Expire = time.Unix(expireTimestamp, 0) - _, l, i := c.findCompatibleFormat(res) + b, l, i := c.findCompatibleFormat(res) if l == 0 { logger.Warn("No compatible formats found for video.") res.Url = "" } else { + videoBuffer := NewVideoBuffer(b, l) + c.buffers.Set(videoId, videoBuffer) res.Url = res.Formats[i].Url }

@@ -72,7 +74,8 @@ }

func (c *Client) NewInstance() error { if c.Instance != "" { - c.timeouts.Set(c.Instance, fmt.Errorf("Generic error")) + err := fmt.Errorf("Generic error") + c.timeouts.Set(c.Instance, &err) } resp, err := c.http.Get(instancesEndpoint)
M invidious/invidious.goinvidious/invidious.go

@@ -1,6 +1,7 @@

package invidious import ( + "bytes" "net/http" "regexp" "strconv"

@@ -10,6 +11,7 @@ "github.com/BiRabittoh/fixyoutube-go/volatile"

"github.com/sirupsen/logrus" ) +const cacheDuration = 5 * time.Minute // 5 m const timeoutDuration = 10 * time.Minute const maxSizeBytes = 20000000 // 20 MB const instancesEndpoint = "https://api.invidious.io/instances.json?sort_by=api,type"

@@ -18,9 +20,15 @@

var expireRegex = regexp.MustCompile(`(?i)expire=(\d+)`) var logger = logrus.New() +type VideoBuffer struct { + buffer *bytes.Buffer + length int64 +} + type Client struct { http *http.Client timeouts *volatile.Volatile[string, error] + buffers *volatile.Volatile[string, VideoBuffer] Instance string }

@@ -53,6 +61,16 @@ }

return res } +func NewVideoBuffer(b *bytes.Buffer, l int64) *VideoBuffer { + duplicate := new(bytes.Buffer) + duplicate.Write(b.Bytes()) + + return &VideoBuffer{ + buffer: duplicate, + length: l, + } +} + func (c *Client) GetVideo(videoId string, fromCache bool) (*Video, error) { logger.Info("Video https://youtu.be/", videoId, " was requested.")

@@ -96,9 +114,11 @@

func NewClient(httpClient *http.Client) *Client { InitDB() timeouts := volatile.NewVolatile[string, error](timeoutDuration) + buffers := volatile.NewVolatile[string, VideoBuffer](cacheDuration) client := &Client{ http: httpClient, timeouts: timeouts, + buffers: buffers, } err := client.NewInstance() if err != nil {
M invidious/proxy.goinvidious/proxy.go

@@ -53,6 +53,8 @@ url := video.Formats[i].Url

logger.Debug(url) b, l, httpStatus := c.urlToBuffer(url) if httpStatus == http.StatusOK { + videoBuffer := NewVideoBuffer(b, l) + c.buffers.Set(video.VideoId, videoBuffer) return b, l, i } logger.Debug("Format ", i, "failed with status code ", httpStatus)

@@ -60,12 +62,26 @@ }

return nil, 0, -1 } +func (c *Client) getBuffer(video Video) (*bytes.Buffer, int64, int) { + vb, err := c.buffers.Get(video.VideoId) + if err != nil { + b, l, s := c.urlToBuffer(video.Url) + if l > 0 { + videoBuffer := NewVideoBuffer(b, l) + c.buffers.Set(video.VideoId, videoBuffer) + } + return b, l, s + } + + videoBuffer := NewVideoBuffer(vb.buffer, vb.length) + return videoBuffer.buffer, videoBuffer.length, http.StatusOK +} + func (c *Client) ProxyVideoId(videoId string) (*bytes.Buffer, int64, int) { video, err := GetVideoDB(videoId) if err != nil { logger.Info("Cannot proxy a video that is not cached: https://youtu.be/", videoId) return nil, 0, http.StatusBadRequest } - - return c.urlToBuffer(video.Url) + return c.getBuffer(*video) }
M volatile/volatile.govolatile/volatile.go

@@ -11,7 +11,7 @@ var logger = logrus.New()

type Element[K comparable, V any] struct { key K - value V + value *V Timestamp time.Time }

@@ -86,41 +86,40 @@ i := v.indexOf(key)

if i == -1 { return nil, fmt.Errorf("Not found") } - return &v.data[i].value, nil + return v.data[i].value, nil } func (v *Volatile[K, V]) Remove(key K) (*V, error) { - err := v.clean() - if err != nil { - logger.Error(err) - return nil, err - } i := v.indexOf(key) if i == -1 { - logger.Error(err) + err := fmt.Errorf("Can't remove unexisting index") + logger.Warn("Trying to delete unexisting key: ", key) return nil, err } value := &v.data[i].value - err = v.removeIndex(i) + err := v.removeIndex(i) + if err != nil { + logger.Error(err) + return nil, err + } + err = v.clean() if err != nil { logger.Error(err) return nil, err } - return value, nil + return *value, nil } -func (v *Volatile[K, V]) Set(key K, value V) error { +func (v *Volatile[K, V]) Set(key K, value *V) error { err := v.clean() if err != nil { logger.Error(err) return err } - _, err = v.Remove(key) - if err != nil { - logger.Error(err) - return err - } + + v.Remove(key) + e := Element[K, V]{key: key, value: value, Timestamp: time.Now()} v.data = append(v.data, e) return nil