[feature] For video attachments, store + return fps, bitrate, duration (#1282)

* start messing about with different mp4 metadata extraction

* heyyooo it works

* add test cow

* move useful multierror to gtserror package

* error out if video doesn't seem to be a real mp4

* test parsing mkv in disguise as mp4

* tidy up error handling

* remove extraneous line

* update framerate formatting

* use float32 for aspect

* fixy mctesterson
This commit is contained in:
tobi
2022-12-22 11:48:28 +01:00
committed by GitHub
parent eabb906268
commit 1659f75ae6
19 changed files with 433 additions and 108 deletions

View File

@@ -22,11 +22,14 @@ import (
"context"
"errors"
"fmt"
"math"
"strconv"
"strings"
"github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/media"
@@ -299,26 +302,38 @@ func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.M
}
// nullable fields
if a.URL != "" {
i := a.URL
if i := a.URL; i != "" {
apiAttachment.URL = &i
}
if a.RemoteURL != "" {
i := a.RemoteURL
if i := a.RemoteURL; i != "" {
apiAttachment.RemoteURL = &i
}
if a.Thumbnail.RemoteURL != "" {
i := a.Thumbnail.RemoteURL
if i := a.Thumbnail.RemoteURL; i != "" {
apiAttachment.PreviewRemoteURL = &i
}
if a.Description != "" {
i := a.Description
if i := a.Description; i != "" {
apiAttachment.Description = &i
}
if i := a.FileMeta.Original.Duration; i != nil {
apiAttachment.Meta.Original.Duration = *i
}
if i := a.FileMeta.Original.Framerate; i != nil {
// the masto api expects this as a string in
// the format `integer/1`, so 30fps is `30/1`
round := math.Round(float64(*i))
fr := strconv.FormatInt(int64(round), 10)
apiAttachment.Meta.Original.FrameRate = fr + "/1"
}
if i := a.FileMeta.Original.Bitrate; i != nil {
apiAttachment.Meta.Original.Bitrate = int(*i)
}
return apiAttachment, nil
}
@@ -789,7 +804,7 @@ func (c *converter) DomainBlockToAPIDomainBlock(ctx context.Context, b *gtsmodel
// convertAttachmentsToAPIAttachments will convert a slice of GTS model attachments to frontend API model attachments, falling back to IDs if no GTS models supplied.
func (c *converter) convertAttachmentsToAPIAttachments(ctx context.Context, attachments []*gtsmodel.MediaAttachment, attachmentIDs []string) ([]model.Attachment, error) {
var errs multiError
var errs gtserror.MultiError
if len(attachments) == 0 {
// GTS model attachments were not populated
@@ -826,7 +841,7 @@ func (c *converter) convertAttachmentsToAPIAttachments(ctx context.Context, atta
// convertEmojisToAPIEmojis will convert a slice of GTS model emojis to frontend API model emojis, falling back to IDs if no GTS models supplied.
func (c *converter) convertEmojisToAPIEmojis(ctx context.Context, emojis []*gtsmodel.Emoji, emojiIDs []string) ([]model.Emoji, error) {
var errs multiError
var errs gtserror.MultiError
if len(emojis) == 0 {
// GTS model attachments were not populated
@@ -863,7 +878,7 @@ func (c *converter) convertEmojisToAPIEmojis(ctx context.Context, emojis []*gtsm
// convertMentionsToAPIMentions will convert a slice of GTS model mentions to frontend API model mentions, falling back to IDs if no GTS models supplied.
func (c *converter) convertMentionsToAPIMentions(ctx context.Context, mentions []*gtsmodel.Mention, mentionIDs []string) ([]model.Mention, error) {
var errs multiError
var errs gtserror.MultiError
if len(mentions) == 0 {
var err error
@@ -895,7 +910,7 @@ func (c *converter) convertMentionsToAPIMentions(ctx context.Context, mentions [
// convertTagsToAPITags will convert a slice of GTS model tags to frontend API model tags, falling back to IDs if no GTS models supplied.
func (c *converter) convertTagsToAPITags(ctx context.Context, tags []*gtsmodel.Tag, tagIDs []string) ([]model.Tag, error) {
var errs multiError
var errs gtserror.MultiError
if len(tags) == 0 {
// GTS model tags were not populated
@@ -929,24 +944,3 @@ func (c *converter) convertTagsToAPITags(ctx context.Context, tags []*gtsmodel.T
return apiTags, errs.Combine()
}
// multiError allows encapsulating multiple errors under a singular instance,
// which is useful when you only want to log on errors, not return early / bubble up.
// TODO: if this is useful elsewhere, move into a separate gts subpackage.
type multiError []string
func (e *multiError) Append(err error) {
*e = append(*e, err.Error())
}
func (e *multiError) Appendf(format string, args ...any) {
*e = append(*e, fmt.Sprintf(format, args...))
}
// Combine converts this multiError to a singular error instance, returning nil if empty.
func (e multiError) Combine() error {
if len(e) == 0 {
return nil
}
return errors.New(`"` + strings.Join(e, `","`) + `"`)
}