Kyoo/transcoder/utils.go
2024-01-19 17:43:01 +01:00

162 lines
3.5 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/labstack/echo/v4"
)
var client = &http.Client{Timeout: 10 * time.Second}
type Item struct {
Path string `json:"path"`
}
func GetPath(resource string, slug string) (string, error) {
url := os.Getenv("API_URL")
if url == "" {
url = "http://back:5000"
}
key := os.Getenv("KYOO_APIKEYS")
if key == "" {
return "", errors.New("missing api keys")
}
key = strings.Split(key, ",")[0]
req, err := http.NewRequest("GET", strings.Join([]string{url, resource, slug}, "/"), nil)
if err != nil {
return "", err
}
req.Header.Set("X-API-KEY", key)
res, err := client.Do(req)
if err != nil {
return "", err
}
if res.StatusCode != 200 {
return "", echo.NewHTTPError(
http.StatusNotFound,
fmt.Sprintf("No %s found with the slug %s.", resource, slug),
)
}
defer res.Body.Close()
ret := Item{}
err = json.NewDecoder(res.Body).Decode(&ret)
if err != nil {
return "", err
}
return ret.Path, nil
}
func SanitizePath(path string) error {
if strings.Contains(path, "/") || strings.Contains(path, "..") {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid parameter. Can't contains path delimiters or ..")
}
return nil
}
func GetClientId(c echo.Context) (string, error) {
key := c.Request().Header.Get("X-CLIENT-ID")
if key == "" {
return "", errors.New("missing client id. Please specify the X-CLIENT-ID header to a guid constant for the lifetime of the player (but unique per instance)")
}
return key, nil
}
func ParseSegment(segment string) (int32, error) {
var ret int32
_, err := fmt.Sscanf(segment, "segment-%d.ts", &ret)
if err != nil {
return 0, echo.NewHTTPError(http.StatusBadRequest, "Could not parse segment.")
}
return ret, nil
}
func ErrorHandler(err error, c echo.Context) {
code := http.StatusInternalServerError
var message string
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
message = fmt.Sprint(he.Message)
} else {
c.Logger().Error(err)
message = "Internal server error"
}
c.JSON(code, struct {
Errors []string `json:"errors"`
}{Errors: []string{message}})
}
func ServeOfflineFile(path string, done <-chan struct{}, c echo.Context) error {
select {
case <-done:
// if the transcode is already finished, no need to do anything, just return the file
return c.File(path)
default:
break
}
var f *os.File
var err error
for {
// wait for the file to be created by the transcoder.
f, err = os.Open(path)
if err == nil {
break
}
time.Sleep(2 * time.Second)
}
defer f.Close()
// Offline transcoding always return a mkv and video/webm allow some browser to play a mkv video
c.Response().Header().Set(echo.HeaderContentType, "video/webm")
c.Response().Header().Set("Trailer", echo.HeaderContentLength)
c.Response().WriteHeader(http.StatusOK)
buffer := make([]byte, 1024)
not_done := true
for not_done {
select {
case <-done:
info, err := f.Stat()
if err != nil {
log.Printf("Stats error %s", err)
return err
}
c.Response().Header().Set(echo.HeaderContentLength, fmt.Sprint(info.Size()))
c.Response().WriteHeader(http.StatusOK)
not_done = false
case <-time.After(5 * time.Second):
}
read:
for {
size, err := f.Read(buffer)
if size == 0 && err == io.EOF {
break read
}
if err != nil && err != io.EOF {
return err
}
_, err = c.Response().Writer.Write(buffer[:size])
if err != nil {
log.Printf("Could not write transcoded file to response.")
return nil
}
}
c.Response().Flush()
}
return nil
}