Skip to content

Commit

Permalink
added mp4 streaming
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirari04 committed May 28, 2024
1 parent 87b94a6 commit 9c512e7
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 77 deletions.
202 changes: 126 additions & 76 deletions controllers/DownloadVideoController.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ func DownloadVideoController(c echo.Context) error {
type Request struct {
UUID string `validate:"required,uuid_rfc4122" param:"UUID"`
QUALITY string `validate:"required,min=1,max=10" param:"QUALITY"`
Stream *bool `validate:"omitempty,boolean" query:"stream"`
}
var requestValidation Request
if status, err := helpers.Validate(c, &requestValidation); err != nil {
return c.String(status, err.Error())
}

if requestValidation.Stream == nil {
requestValidation.Stream = new(bool)
}

reQUALITY := regexp.MustCompile(`^([0-9]{3,4}p|(h264|vp9|av1))$`)
if !reQUALITY.MatchString(requestValidation.QUALITY) {
return c.String(http.StatusBadRequest, "bad quality format")
Expand All @@ -50,24 +55,37 @@ func DownloadVideoController(c echo.Context) error {
files := []string{}
streamIndex := 0

// add subtitles
for _, subtitle := range dbLink.File.Subtitles {
files = append(files, "-i", fmt.Sprintf(
"%s/%s",
subtitle.Path,
subtitle.OutputFile,
))
streamIndex++
if !*requestValidation.Stream {
// add subtitles
for _, subtitle := range dbLink.File.Subtitles {
files = append(files, "-i", fmt.Sprintf(
"%s/%s",
subtitle.Path,
subtitle.OutputFile,
))
streamIndex++
}
}

// add audios
for _, audio := range dbLink.File.Audios {
files = append(files, "-i", fmt.Sprintf(
"%s/%s",
audio.Path,
audio.OutputFile,
))
streamIndex++
if !*requestValidation.Stream {
for _, audio := range dbLink.File.Audios {
files = append(files, "-i", fmt.Sprintf(
"%s/%s",
audio.Path,
audio.OutputFile,
))
streamIndex++
}
} else {
if len(dbLink.File.Audios) > 0 {
files = append(files, "-i", fmt.Sprintf(
"%s/%s",
dbLink.File.Audios[0].Path,
dbLink.File.Audios[0].OutputFile,
))
streamIndex++
}
}

// add video
Expand All @@ -86,87 +104,119 @@ func DownloadVideoController(c echo.Context) error {
files = append(files, "-map", fmt.Sprintf("%d", i))
}

tmpFilePath := fmt.Sprintf("%s/%s-tmp-enc.mkv", config.ENV.FolderVideoUploadsPriv, uuid.NewString())
tmpFilePath := fmt.Sprintf("%s/%s-tmp-enc.mp4", config.ENV.FolderVideoUploadsPriv, uuid.NewString())
defer os.Remove(tmpFilePath)

cmdString := append(files, []string{"-c", "copy", "-f", "matroska", tmpFilePath}...)
var cmdString []string
if !*requestValidation.Stream {
cmdString = append(files, []string{"-c", "copy", "-f", "matroska", tmpFilePath}...)
} else {
cmdString = append(files, []string{"-c", "copy", "-f", "mp4", tmpFilePath}...)
}
cmd := exec.Command("ffmpeg", cmdString...)

if err := cmd.Start(); err != nil {
c.Logger().Error("Failed to run cmd", err)
return nil
}

if *requestValidation.Stream {
if err := cmd.Wait(); err != nil {
c.Logger().Error("Failed to run cmd on wait", err)
return nil
}
} else {
defer cmd.Wait()
}

// wait until file exists
var tmpFile *os.File
var try = 0
for {
if try > 10 {
c.Logger().Error("Failed to receive output file from ffmpeg")
var fileName string
if *requestValidation.Stream {
f, err := os.Open(tmpFilePath)
if err != nil {
c.Logger().Error("Failed to open tmp file", err)
return nil
}
if tmpFile == nil {
f, err := os.Open(tmpFilePath)
if err != nil {
try++
time.Sleep(time.Second * 1)
continue
tmpFile = f
fileName = fmt.Sprintf(
"%s[%s].mp4",
regexp.MustCompile(`[^a-zA-Z0-9]+`).ReplaceAllString(dbLink.Name, "-"),
requestValidation.QUALITY,
)
} else {
var try = 0
for {
if try > 10 {
c.Logger().Error("Failed to receive output file from ffmpeg")
return nil
}
if tmpFile == nil {
f, err := os.Open(tmpFilePath)
if err != nil {
try++
time.Sleep(time.Second * 1)
continue
}
tmpFile = f
break
}
tmpFile = f
break
}
fileName = fmt.Sprintf(
"%s[%s].mkv",
regexp.MustCompile(`[^a-zA-Z0-9]+`).ReplaceAllString(dbLink.Name, "-"),
requestValidation.QUALITY,
)
}
defer tmpFile.Close()

fileName := fmt.Sprintf(
"%s[%s].mkv",
regexp.MustCompile(`[^a-zA-Z0-9]+`).ReplaceAllString(dbLink.Name, "-"),
requestValidation.QUALITY,
)

c.Response().Header().Add("Content-Type", "video/x-matroska")
c.Response().Header().Add("Transfer-Encoding", "chunked")
c.Response().Header().Add("Trailer", "AtEnd")
c.Response().Header().Add("Cache-Control", "no-cache")
c.Response().Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
c.Response().Status = http.StatusOK

var wg sync.WaitGroup
var written int64
var speedA int64 = 10 * 1024
var speedB int64 = 10 * 1024 * 1024
wg.Add(1)
go func() {
defer wg.Done()
for {
timeStart := time.Now().UnixMilli()
n, err := io.CopyN(c.Response().Writer, tmpFile, speedA)
if err != nil {
if err.Error() != "EOF" {
c.Logger().Error("Failed to write to buffer", err)
if *requestValidation.Stream {
c.Response().Header().Add("Accept-Ranges", "bytes")
http.ServeContent(c.Response(), c.Request(), fileName, time.Now(), tmpFile)
} else {
c.Response().Header().Add("Content-Type", "video/x-matroska")
c.Response().Header().Add("Transfer-Encoding", "chunked")
c.Response().Header().Add("Trailer", "AtEnd")
c.Response().Header().Add("Cache-Control", "no-cache")
c.Response().Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
c.Response().Status = http.StatusOK

var wg sync.WaitGroup
var written int64
var speedA int64 = 10 * 1024
var speedB int64 = 10 * 1024 * 1024
wg.Add(1)
go func() {
defer wg.Done()
for {
timeStart := time.Now().UnixMilli()
n, err := io.CopyN(c.Response().Writer, tmpFile, speedA)
if err != nil {
if err.Error() != "EOF" {
c.Logger().Error("Failed to write to buffer", err)
}
break
}
break
}
if n > 0 {
written = written + n
}
c.Response().Flush()
timeEnd := time.Now().UnixMilli()
timeDif := timeEnd - timeStart
// timeout 1 second minus the download time
time.Sleep(time.Second - (time.Millisecond * time.Duration(timeDif)))
// increase speed gradualy
if speedA < speedB {
speedA = speedA * 2
if speedA > speedB {
speedA = speedB
if n > 0 {
written = written + n
}
c.Response().Flush()
timeEnd := time.Now().UnixMilli()
timeDif := timeEnd - timeStart
// timeout 1 second minus the download time
time.Sleep(time.Second - (time.Millisecond * time.Duration(timeDif)))
// increase speed gradualy
if speedA < speedB {
speedA = speedA * 2
if speedA > speedB {
speedA = speedB
}
}
}
}
}()
wg.Wait()
}()
wg.Wait()

cmd.Wait()
c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", c.Response().Size))
cmd.Wait()
c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", c.Response().Size))
}
return nil
}
6 changes: 5 additions & 1 deletion controllers/PlayerController.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"os"
Expand Down Expand Up @@ -54,6 +55,7 @@ func PlayerController(c echo.Context) error {
// List qualitys non hls & check if has some file is ready
var streamIsReady bool
var jsonQualitys []map[string]string
streamUrl := ""
for _, qualiItem := range dbLink.File.Qualitys {
if qualiItem.Ready {
streamIsReady = true
Expand All @@ -63,7 +65,7 @@ func PlayerController(c echo.Context) error {
"height": strconv.Itoa(int(qualiItem.Height)),
"width": strconv.Itoa(int(qualiItem.Width)),
})

streamUrl = fmt.Sprintf("%s/%s/%s/download/video.mkv?stream=1&jwt=%s", config.ENV.FolderVideoQualitysPub, dbLink.UUID, qualiItem.Name, tkn)
}
}
rawQuality, _ := json.Marshal(jsonQualitys)
Expand Down Expand Up @@ -137,6 +139,7 @@ func PlayerController(c echo.Context) error {
"Title": fmt.Sprintf("%s - %s", config.ENV.AppName, dbLink.Name),
"Description": fmt.Sprintf("Watch %s on %s", dbLink.Name, config.ENV.AppName),
"Thumbnail": fmt.Sprintf("%s/%s/image/thumb/%s", config.ENV.FolderVideoQualitysPub, dbLink.UUID, dbLink.File.Thumbnail),
"StreamUrl": template.HTML(streamUrl),
"Width": dbLink.File.Width,
"Height": dbLink.File.Height,
"Qualitys": string(rawQuality),
Expand All @@ -149,5 +152,6 @@ func PlayerController(c echo.Context) error {
"PROJECTURL": config.ENV.Project,
"Folder": config.ENV.FolderVideoQualitysPub,
"JWT": tkn,
"AppName": config.ENV.AppName,
})
}
8 changes: 8 additions & 0 deletions views/player.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@
<meta name="og:image" content="{{ .Thumbnail }}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="{{ .Thumbnail}}" />
<meta property="og:type" content="video">
<meta property="og:url" content="/v/{{.UUID}}">
<meta property="og:video" content="{{.StreamUrl}}">
<meta property="og:video:secure_url" content="{{.StreamUrl}}">
<meta property="og:video:width" content="1920">
<meta property="og:video:height" content="1080">
<meta property="og:site_name" content="{{.AppName}}">
<title>{{ .Title }}</title>
<link rel="preconnect" href="https://cdn.jsdelivr.net" />
<link rel="preconnect" href="https://cdn.plyr.io" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link itemprop="thumbnailUrl" href="{{ .Thumbnail }}">
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.7/plyr.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@5/dark.min.css" />
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet" />
Expand Down

0 comments on commit 9c512e7

Please sign in to comment.