Use a CDN for faster AMV delivery
This commit is contained in:
parent
1b0bb6fdbd
commit
bc4e67f718
70
arn/AMV.go
70
arn/AMV.go
@ -3,6 +3,7 @@ package arn
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/aerogo/nano"
|
||||
"github.com/animenotifier/notify.moe/arn/video"
|
||||
"github.com/minio/minio-go/v6"
|
||||
)
|
||||
|
||||
// AMV is an anime music video.
|
||||
@ -36,16 +38,37 @@ func (amv *AMV) Link() string {
|
||||
return "/amv/" + amv.ID
|
||||
}
|
||||
|
||||
// VideoLink returns the permalink for the video file.
|
||||
func (amv *AMV) VideoLink() string {
|
||||
domain := "arn.sfo2.cdn"
|
||||
|
||||
if amv.IsDraft {
|
||||
domain = "arn.sfo2"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("https://%s.digitaloceanspaces.com/videos/amvs/%s", domain, amv.File)
|
||||
}
|
||||
|
||||
// TitleByUser returns the preferred title for the given user.
|
||||
func (amv *AMV) TitleByUser(user *User) string {
|
||||
return amv.Title.ByUser(user)
|
||||
}
|
||||
|
||||
// SetVideoBytes sets the bytes for the video file.
|
||||
func (amv *AMV) SetVideoBytes(data []byte) error {
|
||||
// SetVideoReader sets the bytes for the video file by reading them from the reader.
|
||||
func (amv *AMV) SetVideoReader(reader io.Reader) error {
|
||||
fileName := amv.ID + ".webm"
|
||||
filePath := path.Join(Root, "videos", "amvs", fileName)
|
||||
err := ioutil.WriteFile(filePath, data, 0644)
|
||||
pattern := amv.ID + ".*.webm"
|
||||
file, err := ioutil.TempFile("", pattern)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := file.Name()
|
||||
defer os.Remove(filePath)
|
||||
|
||||
// Write file contents
|
||||
_, err = io.Copy(file, reader)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@ -53,6 +76,7 @@ func (amv *AMV) SetVideoBytes(data []byte) error {
|
||||
|
||||
// Run mkclean
|
||||
optimizedFile := filePath + ".optimized"
|
||||
defer os.Remove(optimizedFile)
|
||||
|
||||
cmd := exec.Command(
|
||||
"mkclean",
|
||||
@ -79,37 +103,35 @@ func (amv *AMV) SetVideoBytes(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now delete the original file and replace it with the optimized file
|
||||
err = os.Remove(filePath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(optimizedFile, filePath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Refresh video file info
|
||||
amv.File = fileName
|
||||
return amv.RefreshInfo()
|
||||
info, err := video.GetInfo(optimizedFile)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// RefreshInfo refreshes the information about the video file.
|
||||
func (amv *AMV) RefreshInfo() error {
|
||||
if amv.File == "" {
|
||||
return fmt.Errorf("Video file has not been uploaded yet for AMV %s", amv.ID)
|
||||
// Is our storage server available?
|
||||
if Spaces == nil {
|
||||
return errors.New("File storage client has not been initialized")
|
||||
}
|
||||
|
||||
info, err := video.GetInfo(path.Join(Root, "videos", "amvs", amv.File))
|
||||
// Make sure the file is public
|
||||
userMetaData := map[string]string{
|
||||
"x-amz-acl": "public-read",
|
||||
}
|
||||
|
||||
// Upload the file to our storage server
|
||||
_, err = Spaces.FPutObject("arn", fmt.Sprintf("videos/amvs/%s.webm", amv.ID), optimizedFile, minio.PutObjectOptions{
|
||||
ContentType: "video/webm",
|
||||
UserMetadata: userMetaData,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amv.Info = *info
|
||||
amv.File = fileName
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ package arn
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
@ -104,8 +102,8 @@ func (amv *AMV) Delete() error {
|
||||
}
|
||||
|
||||
// Remove file
|
||||
if amv.File != "" {
|
||||
err := os.Remove(path.Join(Root, "videos", "amvs", amv.File))
|
||||
if amv.File != "" && Spaces != nil {
|
||||
err := Spaces.RemoveObject("arn", fmt.Sprintf("videos/amvs/%s", amv.File))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -117,4 +117,7 @@ func init() {
|
||||
// Set Anilist API keys
|
||||
anilist.APIKeyID = APIKeys.AniList.ID
|
||||
anilist.APIKeySecret = APIKeys.AniList.Secret
|
||||
|
||||
// Initialize file storage
|
||||
initSpaces()
|
||||
}
|
||||
|
30
arn/Spaces.go
Normal file
30
arn/Spaces.go
Normal file
@ -0,0 +1,30 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio-go/v6"
|
||||
)
|
||||
|
||||
// Spaces represents our file storage server.
|
||||
var Spaces *minio.Client
|
||||
|
||||
// initSpaces starts our file storage client.
|
||||
func initSpaces() {
|
||||
if APIKeys.S3.ID == "" || APIKeys.S3.Secret == "" {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
endpoint := "sfo2.digitaloceanspaces.com"
|
||||
ssl := true
|
||||
|
||||
// Initiate a client using DigitalOcean Spaces.
|
||||
Spaces, err = minio.New(endpoint, APIKeys.S3.ID, APIKeys.S3.Secret, ssl)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
@ -11,7 +11,7 @@ component AMVMini(amv *arn.AMV, user *arn.User)
|
||||
component AMVVideo(amv *arn.AMV)
|
||||
.video-container(id=amv.ID, data-api="/api/amv/" + amv.ID)
|
||||
video.video.lazy.action(data-action="toggleFullscreen", data-trigger="dblclick", data-id=amv.ID)
|
||||
source(data-src="https://notify.moe/videos/amvs/" + amv.File, data-type="video/webm")
|
||||
source(data-src=amv.VideoLink(), data-type="video/webm")
|
||||
|
||||
//- button.media-play-button
|
||||
//- RawIcon("play")
|
||||
|
@ -2,7 +2,6 @@ package episode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
@ -12,27 +11,6 @@ import (
|
||||
minio "github.com/minio/minio-go/v6"
|
||||
)
|
||||
|
||||
var spaces *minio.Client
|
||||
|
||||
func init() {
|
||||
if arn.APIKeys.S3.ID == "" || arn.APIKeys.S3.Secret == "" {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
endpoint := "sfo2.digitaloceanspaces.com"
|
||||
ssl := true
|
||||
|
||||
// Initiate a client using DigitalOcean Spaces.
|
||||
spaces, err = minio.New(endpoint, arn.APIKeys.S3.ID, arn.APIKeys.S3.Secret, ssl)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Get renders the anime episode.
|
||||
func Get(ctx aero.Context) error {
|
||||
user := utils.GetUser(ctx)
|
||||
@ -60,8 +38,8 @@ func Get(ctx aero.Context) error {
|
||||
// Does the episode exist?
|
||||
uploaded := false
|
||||
|
||||
if spaces != nil {
|
||||
stat, err := spaces.StatObject("arn", fmt.Sprintf("videos/anime/%s/%d.webm", anime.ID, episodeNumber), minio.StatObjectOptions{})
|
||||
if arn.Spaces != nil {
|
||||
stat, err := arn.Spaces.StatObject("arn", fmt.Sprintf("videos/anime/%s/%d.webm", anime.ID, episodeNumber), minio.StatObjectOptions{})
|
||||
uploaded = (err == nil) && (stat.Size > 0)
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ func Subtitles(ctx aero.Context) error {
|
||||
ctx.Response().SetHeader("Access-Control-Allow-Origin", "*")
|
||||
ctx.Response().SetHeader("Content-Type", "text/vtt; charset=utf-8")
|
||||
|
||||
obj, err := spaces.GetObject("arn", fmt.Sprintf("videos/anime/%s/%d.%s.vtt", anime.ID, episodeNumber, language), minio.GetObjectOptions{})
|
||||
obj, err := arn.Spaces.GetObject("arn", fmt.Sprintf("videos/anime/%s/%d.%s.vtt", anime.ID, episodeNumber, language), minio.GetObjectOptions{})
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusInternalServerError, err)
|
||||
|
@ -25,20 +25,17 @@ func AMVFile(ctx aero.Context) error {
|
||||
}
|
||||
|
||||
// Retrieve file from post body
|
||||
data, err := ctx.Request().Body().Bytes()
|
||||
reader := ctx.Request().Body().Reader()
|
||||
defer reader.Close()
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusInternalServerError, "Reading request body failed", err)
|
||||
}
|
||||
|
||||
// Set amv image file
|
||||
err = amv.SetVideoBytes(data)
|
||||
// Set amv video file
|
||||
err = amv.SetVideoReader(reader)
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusInternalServerError, "Invalid video format", err)
|
||||
}
|
||||
|
||||
// Save image information
|
||||
// Save video information
|
||||
amv.Save()
|
||||
|
||||
// Write log entry
|
||||
|
@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/akyoto/color"
|
||||
"github.com/animenotifier/notify.moe/arn"
|
||||
"github.com/animenotifier/notify.moe/arn/stringutils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
color.Yellow("Updating AMV info")
|
||||
|
||||
defer color.Green("Finished.")
|
||||
defer arn.Node.Close()
|
||||
|
||||
for amv := range arn.StreamAMVs() {
|
||||
err := amv.RefreshInfo()
|
||||
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
color.Green(amv.Title.Canonical)
|
||||
stringutils.PrettyPrint(amv.Info)
|
||||
amv.Save()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user