HEX
Server: Apache/2.4.54 (Win64) OpenSSL/1.1.1p PHP/7.4.30
System: Windows NT website-api 10.0 build 20348 (Windows Server 2016) AMD64
User: SYSTEM (0)
PHP: 7.4.30
Disabled: NONE
Upload Files
File: C:/github_repos/casibase_customer_0058/controllers/video.go
// Copyright 2023 The Casibase Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controllers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/beego/beego/utils/pagination"
	"github.com/casibase/casibase/audio"
	"github.com/casibase/casibase/conf"
	"github.com/casibase/casibase/object"
	"github.com/casibase/casibase/storage"
	"github.com/casibase/casibase/util"
	"github.com/casibase/casibase/video"
)

// GetGlobalVideos
// @Title GetGlobalVideos
// @Tag Video API
// @Description get global videos
// @Success 200 {array} object.Video The Response object
// @router /get-global-videos [get]
func (c *ApiController) GetGlobalVideos() {
	videos, err := object.GetGlobalVideos()
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	c.ResponseOk(videos)
}

// GetVideos
// @Title GetVideos
// @Tag Video API
// @Description get videos
// @Param owner query string true "The owner of videos"
// @Success 200 {array} object.Video The Response object
// @router /get-videos [get]
func (c *ApiController) GetVideos() {
	owner := c.Input().Get("owner")
	limit := c.Input().Get("pageSize")
	page := c.Input().Get("p")
	field := c.Input().Get("field")
	value := c.Input().Get("value")
	sortField := c.Input().Get("sortField")
	sortOrder := c.Input().Get("sortOrder")

	owner = ""

	if limit == "" || page == "" {
		videos, err := object.GetVideos(owner, c.GetAcceptLanguage())
		if err != nil {
			c.ResponseError(err.Error())
			return
		}

		c.ResponseOk(videos)
	} else {
		limit := util.ParseInt(limit)
		count, err := object.GetVideoCount(owner, field, value)
		if err != nil {
			c.ResponseError(err.Error())
			return
		}

		paginator := pagination.SetPaginator(c.Ctx, limit, count)
		videos, err := object.GetPaginationVideos(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, c.GetAcceptLanguage())
		if err != nil {
			c.ResponseError(err.Error())
			return
		}

		c.ResponseOk(videos, paginator.Nums())
	}
}

// GetVideo
// @Title GetVideo
// @Tag Video API
// @Description get video
// @Param id query string true "The id of video"
// @Success 200 {object} object.Video The Response object
// @router /get-video [get]
func (c *ApiController) GetVideo() {
	id := c.Input().Get("id")

	video, err := object.GetVideo(id, c.GetAcceptLanguage())
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	if video != nil {
		err = video.Populate(c.GetAcceptLanguage())
		if err != nil {
			c.ResponseError(err.Error())
			return
		}
	}

	c.ResponseOk(video)
}

// UpdateVideo
// @Title UpdateVideo
// @Tag Video API
// @Description update video
// @Param id query string true "The id (owner/name) of the video"
// @Param body body object.Video true "The details of the video"
// @Success 200 {object} controllers.Response The Response object
// @router /update-video [post]
func (c *ApiController) UpdateVideo() {
	user, ok := c.RequireSignedInUser()
	if !ok {
		return
	}

	id := c.Input().Get("id")

	var video object.Video
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &video)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	if user.Type == "video-normal-user" {
		if len(video.Remarks) > 0 || len(video.Remarks2) > 0 || video.State != "Draft" {
			c.ResponseError("The video can only be updated when there are no remarks and the state is \"Draft\"")
			return
		}
	}

	success, err := object.UpdateVideo(id, &video)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	c.ResponseOk(success)
}

// AddVideo
// @Title AddVideo
// @Tag Video API
// @Description add video
// @Param body body object.Video true "The details of the video"
// @Success 200 {object} controllers.Response The Response object
// @router /add-video [post]
func (c *ApiController) AddVideo() {
	var video object.Video
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &video)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	success, err := object.AddVideo(&video)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	c.ResponseOk(success)
}

// DeleteVideo
// @Title DeleteVideo
// @Tag Video API
// @Description delete video
// @Param body body object.Video true "The details of the video"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-video [post]
func (c *ApiController) DeleteVideo() {
	var video object.Video
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &video)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	success, err := object.DeleteVideo(&video)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	c.ResponseOk(success)
}

func updateVideoCoverUrl(id string, videoId string, lang string) error {
	for i := 0; i < 30; i++ {
		coverUrl := video.GetVideoCoverUrl(videoId)
		if coverUrl != "" {
			v, err := object.GetVideo(id, lang)
			if err != nil {
				return err
			}
			if v.CoverUrl != "" {
				break
			}

			v.CoverUrl = coverUrl
			_, err = object.UpdateVideo(id, v)
			if err != nil {
				return err
			}

			break
		}

		time.Sleep(time.Second * 2)
	}

	return nil
}

func startCoverUrlJob(id string, videoId string, lang string) {
	err := object.SetDefaultVodClient(lang)
	if err != nil {
		panic(err)
	}

	go func(id string, videoId string) {
		err = updateVideoCoverUrl(id, videoId, lang)
		if err != nil {
			panic(err)
		}
	}(id, videoId)
}

func copyBuffer(original *bytes.Buffer) *bytes.Buffer {
	bufCopy := make([]byte, original.Len())
	copy(bufCopy, original.Bytes())
	return bytes.NewBuffer(bufCopy)
}

func getSpeaker(s string) string {
	if s == "0" {
		return "Teacher"
	} else if s == "1" {
		return "Student"
	} else {
		return fmt.Sprintf("Student %s", s)
	}
}

func getAudioSegments(userName string, filename string, fileBuffer *bytes.Buffer, lang string) (string, []*object.Label, error) {
	audioStorageProviderName := conf.GetConfigString("audioStorageProvider")
	if audioStorageProviderName == "" {
		return "", []*object.Label{}, nil
	}

	fileBuffer2 := copyBuffer(fileBuffer)

	audioBuffer, err := audio.GetAudioFromVideo(fileBuffer2)
	if err != nil {
		return "", nil, err
	}

	audioStorageProvider, err := storage.NewCasdoorProvider(audioStorageProviderName, lang)
	if err != nil {
		return "", nil, err
	}

	audioFilename := strings.Replace(filename, ".mp4", ".mp3", 1)
	audioUrl, err := audioStorageProvider.PutObject(userName, "Uploaded-Audio", audioFilename, audioBuffer)
	if err != nil {
		return "", nil, err
	}

	tmpInputFile, err := os.CreateTemp("", "casibase-audio-*.mp3")
	if err != nil {
		return "", nil, err
	}
	defer os.Remove(tmpInputFile.Name())

	_, err = io.Copy(tmpInputFile, audioBuffer)
	if err != nil {
		return "", nil, err
	}
	tmpInputFile.Close()

	segments := []*object.Label{}
	oSegments, err := audio.GetSegmentsFromAudio(tmpInputFile.Name(), lang)
	if err != nil {
		return "", nil, err
	}

	for i, item := range oSegments {
		segment := &object.Label{
			Id:        strconv.Itoa(i),
			StartTime: util.ParseFloat(item.Bg) / 1000,
			EndTime:   util.ParseFloat(item.Ed) / 1000,
			Text:      item.Onebest,
			Speaker:   getSpeaker(item.Speaker),
		}
		segments = append(segments, segment)
	}

	return audioUrl, segments, nil
}

// UploadVideo
// @Title UploadVideo
// @Tag Video API
// @Description upload video
// @Param file formData file true "The video file to upload"
// @Success 200 {object} string "The fileId of the uploaded video"
// @router /upload-video [post]
func (c *ApiController) UploadVideo() {
	userName, ok := c.RequireSignedIn()
	if !ok {
		return
	}

	file, header, err := c.GetFile("file")
	if err != nil {
		c.ResponseError(err.Error())
		return
	}
	defer file.Close()

	filename := header.Filename
	fileId := util.RemoveExt(filename)
	ext := filepath.Ext(filename)

	fileBuffer := bytes.NewBuffer(nil)
	_, err = io.Copy(fileBuffer, file)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	fileType := "unknown"
	contentType := header.Header.Get("Content-Type")
	fileType, _ = util.GetOwnerAndNameFromId(contentType)
	if fileType != "video" {
		c.ResponseError(fmt.Sprintf("contentType: %s is not video", contentType))
		return
	}

	err = object.SetDefaultVodClient(c.GetAcceptLanguage())
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	videoId, err := video.UploadVideo(fileId, filename, fileBuffer)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}
	if videoId == "" {
		c.ResponseError("UploadVideo() error, videoId should not be empty")
		return
	}

	audioUrl, segments, err := getAudioSegments(userName, filename, fileBuffer, c.GetAcceptLanguage())
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	v := &object.Video{
		Owner:       userName,
		Name:        fileId,
		CreatedTime: util.GetCurrentTime(),
		DisplayName: fileId,
		Tag:         "",
		Type:        ext,
		VideoId:     videoId,
		CoverUrl:    "",
		AudioUrl:    audioUrl,
		Labels:      []*object.Label{},
		Segments:    segments,
		DataUrls:    []string{},
		DataUrl:     "",
		TagOnPause:  false,
		Remarks:     []*object.Remark{},
		Remarks2:    []*object.Remark{},
		State:       "Draft",
		Keywords:    []string{},
	}
	_, err = object.AddVideo(v)
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	id := util.GetIdFromOwnerAndName(userName, fileId)

	err = updateVideoCoverUrl(id, videoId, c.GetAcceptLanguage())
	if err != nil {
		c.ResponseError(err.Error())
		return
	}

	// startCoverUrlJob(id, videoId)

	c.ResponseOk(fileId)
}