283 lines
5.9 KiB
Go
Raw Normal View History

2019-06-03 09:32:43 +00:00
package arn
import (
"errors"
"fmt"
2019-08-28 01:07:50 +00:00
"io"
2019-06-03 09:32:43 +00:00
"os"
2024-08-09 10:09:51 +00:00
"path/filepath"
2019-06-03 09:32:43 +00:00
"github.com/aerogo/nano"
"github.com/animenotifier/notify.moe/arn/video"
)
// AMV is an anime music video.
type AMV struct {
File string `json:"file" editable:"true" type:"upload" filetype:"video" endpoint:"/api/upload/amv/:id/file"`
Title AMVTitle `json:"title" editable:"true"`
2019-11-18 05:01:13 +00:00
MainAnimeID AnimeID `json:"mainAnimeId" editable:"true"`
ExtraAnimeIDs []AnimeID `json:"extraAnimeIds" editable:"true"`
VideoEditorIDs []UserID `json:"videoEditorIds" editable:"true"`
2019-06-03 09:32:43 +00:00
Links []Link `json:"links" editable:"true"`
Tags []string `json:"tags" editable:"true"`
Info video.Info `json:"info"`
hasID
hasPosts
hasCreator
hasEditor
hasLikes
hasDraft
}
// Link returns the permalink for the AMV.
func (amv *AMV) Link() string {
return "/amv/" + amv.ID
}
2019-08-28 01:07:50 +00:00
// VideoLink returns the permalink for the video file.
func (amv *AMV) VideoLink() string {
2024-08-09 10:09:51 +00:00
return fmt.Sprintf("https://notify.moe/videos/amvs/%s", amv.File)
2019-08-28 01:07:50 +00:00
}
2019-06-03 09:32:43 +00:00
// TitleByUser returns the preferred title for the given user.
func (amv *AMV) TitleByUser(user *User) string {
return amv.Title.ByUser(user)
}
2019-08-28 01:07:50 +00:00
// SetVideoReader sets the bytes for the video file by reading them from the reader.
func (amv *AMV) SetVideoReader(reader io.Reader) error {
2019-06-03 09:32:43 +00:00
fileName := amv.ID + ".webm"
2024-08-09 10:09:51 +00:00
file, err := os.Create(filepath.Join(Root, "videos", "amvs", fileName))
2019-08-28 01:07:50 +00:00
if err != nil {
return err
}
2024-08-09 10:09:51 +00:00
defer file.Close()
2019-08-28 01:07:50 +00:00
_, err = io.Copy(file, reader)
2019-06-03 09:32:43 +00:00
if err != nil {
return err
}
// Run mkclean
2024-08-09 10:09:51 +00:00
// optimizedFile := filePath + ".optimized"
// defer os.Remove(optimizedFile)
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// cmd := exec.Command(
// "mkclean",
// "--doctype", "4",
// "--keep-cues",
// "--optimize",
// filePath,
// optimizedFile,
// )
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
// cmd.Stdin = os.Stdin
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// err = cmd.Start()
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// if err != nil {
// return err
// }
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// err = cmd.Wait()
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// if err != nil {
// return err
// }
2019-06-03 09:32:43 +00:00
2019-08-28 01:07:50 +00:00
// Refresh video file info
2024-08-09 10:09:51 +00:00
// info, err := video.GetInfo(optimizedFile)
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// if err != nil {
// return err
// }
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// // Is our storage server available?
// if Spaces == nil {
// return errors.New("File storage client has not been initialized")
// }
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// // Make sure the file is public
// userMetaData := map[string]string{
// "x-amz-acl": "public-read",
// }
2019-06-03 09:32:43 +00:00
2024-08-09 10:09:51 +00:00
// // Upload the file to our storage server
// _, err = Spaces.FPutObject(context.TODO(), "arn", , optimizedFile, minio.PutObjectOptions{
// ContentType: "video/webm",
// UserMetadata: userMetaData,
// })
// Refresh video file info
info, err := video.GetInfo(fileName)
2019-06-03 09:32:43 +00:00
if err != nil {
return err
}
amv.Info = *info
2019-08-28 01:07:50 +00:00
amv.File = fileName
2019-06-03 09:32:43 +00:00
return nil
}
// MainAnime returns main anime for the AMV, if available.
func (amv *AMV) MainAnime() *Anime {
mainAnime, _ := GetAnime(amv.MainAnimeID)
return mainAnime
}
// ExtraAnime returns main anime for the AMV, if available.
func (amv *AMV) ExtraAnime() []*Anime {
objects := DB.GetMany("Anime", amv.ExtraAnimeIDs)
2019-08-28 08:06:42 +00:00
animes := make([]*Anime, 0, len(amv.ExtraAnimeIDs))
2019-06-03 09:32:43 +00:00
for _, obj := range objects {
if obj == nil {
continue
}
animes = append(animes, obj.(*Anime))
}
return animes
}
// VideoEditors returns a slice of all the users involved in creating the AMV.
func (amv *AMV) VideoEditors() []*User {
objects := DB.GetMany("User", amv.VideoEditorIDs)
editors := []*User{}
for _, obj := range objects {
if obj == nil {
continue
}
editors = append(editors, obj.(*User))
}
return editors
}
// Publish turns the draft into a published object.
func (amv *AMV) Publish() error {
// No title
if amv.Title.String() == "" {
return errors.New("AMV doesn't have a title")
}
// No anime found
if amv.MainAnimeID == "" && len(amv.ExtraAnimeIDs) == 0 {
return errors.New("Need to specify at least one anime")
}
2019-09-06 05:26:46 +00:00
// Check that the file name exists
2019-06-03 09:32:43 +00:00
if amv.File == "" {
return errors.New("You need to upload a WebM file for this AMV")
}
2019-09-06 05:26:46 +00:00
// No file uploaded
2024-08-09 10:09:51 +00:00
_, err := os.Stat(filepath.Join(Root, "videos", "amvs", amv.File))
2019-09-06 05:26:46 +00:00
if err != nil {
2019-06-03 09:32:43 +00:00
return errors.New("You need to upload a WebM file for this AMV")
}
return publish(amv)
}
// Unpublish turns the object back into a draft.
func (amv *AMV) Unpublish() error {
return unpublish(amv)
}
// OnLike is called when the AMV receives a like.
func (amv *AMV) OnLike(likedBy *User) {
if likedBy.ID == amv.CreatedBy {
return
}
go func() {
amv.Creator().SendNotification(&PushNotification{
Title: likedBy.Nick + " liked your AMV " + amv.Title.ByUser(amv.Creator()),
Message: likedBy.Nick + " liked your AMV " + amv.Title.ByUser(amv.Creator()) + ".",
Icon: "https:" + likedBy.AvatarLink("large"),
Link: "https://notify.moe" + likedBy.Link(),
Type: NotificationTypeLike,
})
}()
}
// String implements the default string serialization.
func (amv *AMV) String() string {
return amv.Title.ByUser(nil)
}
// TypeName returns the type name.
func (amv *AMV) TypeName() string {
return "AMV"
}
// Self returns the object itself.
func (amv *AMV) Self() Loggable {
return amv
}
// GetAMV returns the AMV with the given ID.
2019-11-18 06:13:51 +00:00
func GetAMV(id ID) (*AMV, error) {
2019-06-03 09:32:43 +00:00
obj, err := DB.Get("AMV", id)
if err != nil {
return nil, err
}
return obj.(*AMV), nil
}
// StreamAMVs returns a stream of all AMVs.
func StreamAMVs() <-chan *AMV {
channel := make(chan *AMV, nano.ChannelBufferSize)
go func() {
for obj := range DB.All("AMV") {
channel <- obj.(*AMV)
}
close(channel)
}()
return channel
}
// AllAMVs returns a slice of all AMVs.
func AllAMVs() []*AMV {
all := make([]*AMV, 0, DB.Collection("AMV").Count())
stream := StreamAMVs()
for obj := range stream {
all = append(all, obj)
}
return all
}
// FilterAMVs filters all AMVs by a custom function.
func FilterAMVs(filter func(*AMV) bool) []*AMV {
var filtered []*AMV
for obj := range DB.All("AMV") {
realObject := obj.(*AMV)
if filter(realObject) {
filtered = append(filtered, realObject)
}
}
return filtered
}