[feature] Update attachment format, receive + send focalPoint prop + use it on the frontend (#4052)

* [feature] Update attachment format, receive + send `focalPoint` prop + use it on the frontend

* whoops

* boop

* restore function signature of ExtractAttachments
This commit is contained in:
tobi 2025-04-26 15:03:05 +02:00 committed by GitHub
commit f7323c065a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 617 additions and 72 deletions

View file

@ -678,22 +678,9 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
status.SetActivityStreamsContent(contentProp)
// attachments
attachmentProp := streams.NewActivityStreamsAttachmentProperty()
attachments := s.Attachments
if len(s.AttachmentIDs) != len(attachments) {
attachments, err = c.state.DB.GetAttachmentsByIDs(ctx, s.AttachmentIDs)
if err != nil {
return nil, gtserror.Newf("error getting attachments from database: %w", err)
}
if err := c.attachAttachments(ctx, s, status); err != nil {
return nil, gtserror.Newf("error attaching attachments: %w", err)
}
for _, a := range attachments {
doc, err := c.AttachmentToAS(ctx, a)
if err != nil {
return nil, gtserror.Newf("error converting attachment: %w", err)
}
attachmentProp.AppendActivityStreamsDocument(doc)
}
status.SetActivityStreamsAttachment(attachmentProp)
// replies
repliesCollection, err := c.StatusToASRepliesCollection(ctx, s, false)
@ -1130,39 +1117,94 @@ func (c *Converter) EmojiToAS(ctx context.Context, e *gtsmodel.Emoji) (vocab.Too
return emoji, nil
}
// AttachmentToAS converts a gts model media attachment into an activity streams Attachment, suitable for federation
func (c *Converter) AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) {
// type -- Document
doc := streams.NewActivityStreamsDocument()
// mediaType aka mime content type
mediaTypeProp := streams.NewActivityStreamsMediaTypeProperty()
mediaTypeProp.Set(a.File.ContentType)
doc.SetActivityStreamsMediaType(mediaTypeProp)
// url -- for the original image not the thumbnail
urlProp := streams.NewActivityStreamsUrlProperty()
imageURL, err := url.Parse(a.URL)
if err != nil {
return nil, fmt.Errorf("AttachmentToAS: error parsing uri %s: %s", a.URL, err)
// attachAttachments converts the attachments on the given status
// into Attachmentables, and appends them to the given Statusable.
func (c *Converter) attachAttachments(
ctx context.Context,
s *gtsmodel.Status,
statusable ap.Statusable,
) error {
// Ensure status attachments populated.
if len(s.AttachmentIDs) != len(s.Attachments) {
var err error
s.Attachments, err = c.state.DB.GetAttachmentsByIDs(ctx, s.AttachmentIDs)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return gtserror.Newf("db error getting attachments: %w", err)
}
}
urlProp.AppendIRI(imageURL)
doc.SetActivityStreamsUrl(urlProp)
// name -- aka image description
nameProp := streams.NewActivityStreamsNameProperty()
nameProp.AppendXMLSchemaString(a.Description)
doc.SetActivityStreamsName(nameProp)
// Prepare attachment property.
attachmentProp := streams.NewActivityStreamsAttachmentProperty()
defer statusable.SetActivityStreamsAttachment(attachmentProp)
// blurhash
blurProp := streams.NewTootBlurhashProperty()
blurProp.Set(a.Blurhash)
doc.SetTootBlurhash(blurProp)
for _, a := range s.Attachments {
// focalpoint
// TODO
// Use appropriate vocab.Type and
// append function for this attachment.
var (
attachmentable ap.Attachmentable
append func()
)
switch a.Type {
return doc, nil
// png, gif, webp, jpeg, etc.
case gtsmodel.FileTypeImage:
t := streams.NewActivityStreamsImage()
attachmentable = t
append = func() { attachmentProp.AppendActivityStreamsImage(t) }
// mp4, m4a, wmv, webm, etc.
case gtsmodel.FileTypeVideo, gtsmodel.FileTypeGifv:
t := streams.NewActivityStreamsVideo()
attachmentable = t
append = func() { attachmentProp.AppendActivityStreamsVideo(t) }
// mp3, flac, ogg, wma, etc.
case gtsmodel.FileTypeAudio:
t := streams.NewActivityStreamsAudio()
attachmentable = t
append = func() { attachmentProp.AppendActivityStreamsAudio(t) }
// Not sure, fall back to Document.
default:
t := streams.NewActivityStreamsDocument()
attachmentable = t
append = func() { attachmentProp.AppendActivityStreamsDocument(t) }
}
// `mediaType` ie., mime content type.
ap.SetMediaType(attachmentable, a.File.ContentType)
// URL of the media file.
imageURL, err := url.Parse(a.URL)
if err != nil {
return gtserror.Newf("error parsing attachment url: %w", err)
}
ap.AppendURL(attachmentable, imageURL)
// `summary` ie., media description / alt text
ap.AppendSummary(attachmentable, a.Description)
// `blurhash`
ap.SetBlurhash(attachmentable, a.Blurhash)
// Set `focalPoint` only if necessary.
if a.FileMeta.Focus.X != 0 && a.FileMeta.Focus.Y != 0 {
if withFocalPoint, ok := attachmentable.(ap.WithFocalPoint); ok {
focalPointProp := streams.NewTootFocalPointProperty()
focalPointProp.AppendXMLSchemaFloat(float64(a.FileMeta.Focus.X))
focalPointProp.AppendXMLSchemaFloat(float64(a.FileMeta.Focus.Y))
withFocalPoint.SetTootFocalPoint(focalPointProp)
}
}
// Done, append
// to Statusable.
append()
}
statusable.SetActivityStreamsAttachment(attachmentProp)
return nil
}
// FaveToAS converts a gts model status fave into an activityStreams LIKE, suitable for federation.

View file

@ -597,6 +597,10 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() {
"Emoji": "toot:Emoji",
"Hashtag": "as:Hashtag",
"blurhash": "toot:blurhash",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#"
}
@ -604,9 +608,13 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() {
"attachment": [
{
"blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj",
"focalPoint": [
-0.5,
0.5
],
"mediaType": "image/jpeg",
"name": "Black and white image of some 50's style text saying: Welcome On Board",
"type": "Document",
"summary": "Black and white image of some 50's style text saying: Welcome On Board",
"type": "Image",
"url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg"
}
],
@ -697,6 +705,10 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() {
"Emoji": "toot:Emoji",
"Hashtag": "as:Hashtag",
"blurhash": "toot:blurhash",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#"
}
@ -704,9 +716,13 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() {
"attachment": [
{
"blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj",
"focalPoint": [
-0.5,
0.5
],
"mediaType": "image/jpeg",
"name": "Black and white image of some 50's style text saying: Welcome On Board",
"type": "Document",
"summary": "Black and white image of some 50's style text saying: Welcome On Board",
"type": "Image",
"url": "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg"
}
],

View file

@ -553,8 +553,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
"x": -0.5,
"y": 0.5
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",
@ -701,8 +701,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendHTMLContentWarning
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
"x": -0.5,
"y": 0.5
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",
@ -851,8 +851,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendApplicationDeleted
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
"x": -0.5,
"y": 0.5
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",
@ -1032,8 +1032,8 @@ func (suite *InternalToFrontendTestSuite) TestWarnFilteredStatusToFrontend() {
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
"x": -0.5,
"y": 0.5
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",
@ -1218,8 +1218,8 @@ func (suite *InternalToFrontendTestSuite) TestWarnFilteredBoostToFrontend() {
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
"x": -0.5,
"y": 0.5
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",
@ -1955,8 +1955,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontendUnknownLanguage()
"aspect": 1.9104477
},
"focus": {
"x": 0,
"y": 0
"x": -0.5,
"y": 0.5
}
},
"description": "Black and white image of some 50's style text saying: Welcome On Board",