/ internal / video.go
video.go
  1  package utils
  2  
  3  import (
  4  	"context"
  5  	"fmt"
  6  	"io"
  7  	"log"
  8  	"net/http"
  9  	"os"
 10  
 11  	"github.com/h2non/filetype"
 12  	"github.com/labstack/echo/v4"
 13  	"github.com/minio/minio-go/v7"
 14  	"github.com/minio/minio-go/v7/pkg/credentials"
 15  )
 16  
 17  type (
 18  	MediaController struct {
 19  		s3 S3
 20  	}
 21  )
 22  
 23  func NewMediaController(s3 S3) *MediaController {
 24  	return &MediaController{s3}
 25  }
 26  
 27  func (mc MediaController) ExtractFromS3(c echo.Context) error {
 28  	media_id := c.Param("id")
 29  	fmt.Println("media_id : ", media_id)
 30  
 31  	if _, err := os.Stat(mc.s3.Tmp + media_id + ".lock"); err == nil {
 32  		fmt.Println("File is being writing")
 33  		return c.File(mc.s3.Directory + "static/videos/tmp.mp4")
 34  	}
 35  
 36  	if _, err := os.Stat(mc.s3.Tmp + media_id); err == nil {
 37  		fmt.Printf("File exists\n")
 38  		tmpFile, err := os.OpenFile(mc.s3.Tmp+media_id, os.O_RDONLY, 0644)
 39  		defer tmpFile.Close()
 40  		if err != nil {
 41  			log.Println("error on open file : ", err)
 42  		}
 43  		return c.File(tmpFile.Name())
 44  	} else {
 45  		fmt.Printf("File does not exist\n")
 46  
 47  		f, err := os.Create(mc.s3.Tmp + media_id + ".lock")
 48  		f.Close()
 49  		defer os.Remove(mc.s3.Tmp + media_id + ".lock")
 50  
 51  		endpoint := mc.s3.Endpoint
 52  		accessKeyID := mc.s3.AccessKeyID
 53  		secretAccessKey := mc.s3.SecretAccessKey
 54  		region := mc.s3.Region
 55  		useSSL := true
 56  
 57  		bucketName := mc.s3.Bucket
 58  		objectName := mc.s3.Directory + media_id
 59  
 60  		// Initialize minio client object.
 61  		minioClient, err := minio.New(endpoint, &minio.Options{
 62  			Region: region,
 63  			Creds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
 64  			Secure: useSSL,
 65  		})
 66  		if err != nil {
 67  			log.Println(err)
 68  			return c.String(http.StatusNotFound, "media not found")
 69  		}
 70  
 71  		object, err := minioClient.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
 72  		if err != nil {
 73  			log.Println(err)
 74  			return c.String(http.StatusNotFound, "media not found")
 75  		}
 76  		defer object.Close()
 77  
 78  		tmpFile, err := os.OpenFile(mc.s3.Tmp+media_id, os.O_RDWR|os.O_CREATE, 0644)
 79  		defer tmpFile.Close()
 80  		// Check for errors when opening the file
 81  		if err != nil {
 82  			log.Println("error on open file : ", err)
 83  			return c.String(http.StatusInternalServerError, "Error on open file: "+err.Error())
 84  		}
 85  
 86  		//fmt.Println("before copy S3 file to tmpFile")
 87  		if _, err := io.Copy(tmpFile, object); err != nil {
 88  			os.Remove(tmpFile.Name())
 89  			log.Println(err)
 90  			return c.String(http.StatusNotFound, "media not found")
 91  		}
 92  		//fmt.Println("after copy S3 file to tmpFile")
 93  		return c.File(tmpFile.Name())
 94  	}
 95  }
 96  
 97  // https://medium.com/@radhian.amri/video-streaming-using-http-206-partial-content-in-go-4e89d96abdd0
 98  func (mc MediaController) PlayFrontMediaFromHTTP(c echo.Context) error {
 99  	media_id := c.Param("id")
100  	//fmt.Println("media_id : ", media_id)
101  
102  	req, _ := http.NewRequest("GET", "http://127.0.0.1:8079/media/"+media_id, nil)
103  	client := &http.Client{}
104  	req.Header.Set("Range", c.Request().Header.Get("Range"))
105  	resp, err := client.Do(req)
106  	if err != nil {
107  		log.Println(err)
108  		return c.Redirect(http.StatusNotFound, "/404")
109  	}
110  	if resp.StatusCode == http.StatusLocked {
111  		log.Println("media is LOCKED")
112  		return c.Redirect(http.StatusLocked, "/")
113  	}
114  	if resp.StatusCode == http.StatusNotFound {
115  		log.Println("media NOT found")
116  		return c.Redirect(http.StatusTemporaryRedirect, "/404")
117  	}
118  
119  	var l_contentType_ext, l_contentType_mime string
120  	if _, err := os.Stat(mc.s3.Tmp + media_id + ".lock"); err == nil {
121  		fmt.Println("using tmp.mp4")
122  		l_contentType_mime = "video/mp4"
123  	} else {
124  		l_contentType_ext, l_contentType_mime = mc.getMimeType(media_id)
125  		fmt.Printf("File type: %s. MIME: %s\n", l_contentType_ext, l_contentType_mime)
126  	}
127  
128  	c.Response().Header().Set(echo.HeaderContentLength, resp.Header.Get("Content-Length"))
129  	contentRange := resp.Header.Get("Content-Range")
130  	if contentRange != "" {
131  		c.Response().Header().Set("Content-Range", contentRange)
132  	}
133  	defer resp.Body.Close()
134  	return c.Stream(resp.StatusCode, l_contentType_mime, resp.Body)
135  }
136  
137  func (mc MediaController) getMimeType(media_id string) (string, string) {
138  	dStream, err := os.OpenFile("/tmp/"+media_id, os.O_RDONLY, 0644)
139  	defer dStream.Close()
140  	// Check for errors when opening the file
141  	if err != nil {
142  		log.Println("error on open file : ", err.Error())
143  		return "", ""
144  	}
145  
146  	fileBytes := make([]byte, 261)
147  	if _, err := dStream.Read(fileBytes); err != nil {
148  		panic(err)
149  	}
150  	contentType, _ := filetype.Match(fileBytes)
151  
152  	if contentType == filetype.Unknown {
153  		fmt.Println("Unknown file type")
154  	}
155  
156  	return contentType.Extension, contentType.MIME.Value
157  }