mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-27 19:01:01 +00:00
[feature/frontend] Add player for audio files; use thumbnail for poster
(#3099)
* [feature/frontend] Audio player for audio media types * use video preview images for previews instead of video itself * don't preload * update tests for new zork status * collapse media gallery into single row when small
This commit is contained in:
parent
16421f7576
commit
9efb11d848
26 changed files with 327 additions and 95 deletions
|
@ -82,7 +82,7 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() {
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
"first": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40",
|
"first": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40",
|
||||||
"id": "http://localhost:8080/users/the_mighty_zork/outbox",
|
"id": "http://localhost:8080/users/the_mighty_zork/outbox",
|
||||||
"totalItems": 7,
|
"totalItems": 8,
|
||||||
"type": "OrderedCollection"
|
"type": "OrderedCollection"
|
||||||
}`, dst.String())
|
}`, dst.String())
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() {
|
||||||
],
|
],
|
||||||
"partOf": "http://localhost:8080/users/the_mighty_zork/outbox",
|
"partOf": "http://localhost:8080/users/the_mighty_zork/outbox",
|
||||||
"prev": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40\u0026min_id=01HH9KYNQPA416TNJ53NSATP40",
|
"prev": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40\u0026min_id=01HH9KYNQPA416TNJ53NSATP40",
|
||||||
"totalItems": 7,
|
"totalItems": 8,
|
||||||
"type": "OrderedCollectionPage"
|
"type": "OrderedCollectionPage"
|
||||||
}`, dst.String())
|
}`, dst.String())
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {
|
||||||
"id": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40&max_id=01F8MHAMCHF6Y650WCRSCP4WMY",
|
"id": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40&max_id=01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||||
"orderedItems": [],
|
"orderedItems": [],
|
||||||
"partOf": "http://localhost:8080/users/the_mighty_zork/outbox",
|
"partOf": "http://localhost:8080/users/the_mighty_zork/outbox",
|
||||||
"totalItems": 7,
|
"totalItems": 8,
|
||||||
"type": "OrderedCollectionPage"
|
"type": "OrderedCollectionPage"
|
||||||
}`, dst.String())
|
}`, dst.String())
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {
|
||||||
suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", apimodelAccount.HeaderStatic)
|
suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", apimodelAccount.HeaderStatic)
|
||||||
suite.Equal(2, apimodelAccount.FollowersCount)
|
suite.Equal(2, apimodelAccount.FollowersCount)
|
||||||
suite.Equal(2, apimodelAccount.FollowingCount)
|
suite.Equal(2, apimodelAccount.FollowingCount)
|
||||||
suite.Equal(7, apimodelAccount.StatusesCount)
|
suite.Equal(8, apimodelAccount.StatusesCount)
|
||||||
suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy)
|
suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy)
|
||||||
suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language)
|
suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language)
|
||||||
suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note)
|
suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note)
|
||||||
|
|
|
@ -240,8 +240,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"enable_rss": true,
|
"enable_rss": true,
|
||||||
|
|
|
@ -135,7 +135,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.png",
|
"thumbnail": "http://localhost:8080/assets/logo.png",
|
||||||
|
@ -256,7 +256,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.png",
|
"thumbnail": "http://localhost:8080/assets/logo.png",
|
||||||
|
@ -377,7 +377,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.png",
|
"thumbnail": "http://localhost:8080/assets/logo.png",
|
||||||
|
@ -549,7 +549,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.png",
|
"thumbnail": "http://localhost:8080/assets/logo.png",
|
||||||
|
@ -692,7 +692,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/original/`+instanceAccount.AvatarMediaAttachment.ID+`.gif",`+`
|
"thumbnail": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/original/`+instanceAccount.AvatarMediaAttachment.ID+`.gif",`+`
|
||||||
|
@ -850,7 +850,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.png",
|
"thumbnail": "http://localhost:8080/assets/logo.png",
|
||||||
|
|
|
@ -916,7 +916,7 @@ func (suite *SearchGetTestSuite) TestSearchAAny() {
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.Len(searchResult.Accounts, 5)
|
suite.Len(searchResult.Accounts, 5)
|
||||||
suite.Len(searchResult.Statuses, 6)
|
suite.Len(searchResult.Statuses, 7)
|
||||||
suite.Len(searchResult.Hashtags, 0)
|
suite.Len(searchResult.Hashtags, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,7 +959,7 @@ func (suite *SearchGetTestSuite) TestSearchAAnyFollowingOnly() {
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.Len(searchResult.Accounts, 2)
|
suite.Len(searchResult.Accounts, 2)
|
||||||
suite.Len(searchResult.Statuses, 6)
|
suite.Len(searchResult.Statuses, 7)
|
||||||
suite.Len(searchResult.Hashtags, 0)
|
suite.Len(searchResult.Hashtags, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1002,7 +1002,7 @@ func (suite *SearchGetTestSuite) TestSearchAStatuses() {
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.Len(searchResult.Accounts, 0)
|
suite.Len(searchResult.Accounts, 0)
|
||||||
suite.Len(searchResult.Statuses, 6)
|
suite.Len(searchResult.Statuses, 7)
|
||||||
suite.Len(searchResult.Hashtags, 0)
|
suite.Len(searchResult.Hashtags, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,8 +114,8 @@ func (suite *StatusHistoryTestSuite) TestGetHistory() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"enable_rss": true,
|
"enable_rss": true,
|
||||||
|
|
|
@ -132,8 +132,8 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"enable_rss": true,
|
"enable_rss": true,
|
||||||
|
@ -197,8 +197,8 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"enable_rss": true,
|
"enable_rss": true,
|
||||||
|
|
|
@ -90,12 +90,23 @@ type Attachment struct {
|
||||||
// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet.
|
// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet.
|
||||||
// See https://github.com/woltapp/blurhash
|
// See https://github.com/woltapp/blurhash
|
||||||
Blurhash *string `json:"blurhash"`
|
Blurhash *string `json:"blurhash"`
|
||||||
|
}
|
||||||
|
|
||||||
// Additional fields not exposed via JSON
|
// WebAttachment is like Attachment, but with
|
||||||
// (used only internally for templating etc).
|
// additional fields not exposed via JSON;
|
||||||
|
// used only internally for templating etc.
|
||||||
|
//
|
||||||
|
// swagger:ignore
|
||||||
|
type WebAttachment struct {
|
||||||
|
*Attachment
|
||||||
|
|
||||||
// Parent status of this media is sensitive.
|
// Parent status of this
|
||||||
Sensitive bool `json:"-"`
|
// media is sensitive.
|
||||||
|
Sensitive bool
|
||||||
|
|
||||||
|
// MIME type of
|
||||||
|
// the attachment.
|
||||||
|
MIMEType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MediaMeta models media metadata.
|
// MediaMeta models media metadata.
|
||||||
|
|
|
@ -111,6 +111,10 @@ type Status struct {
|
||||||
type WebStatus struct {
|
type WebStatus struct {
|
||||||
*Status
|
*Status
|
||||||
|
|
||||||
|
// Web version of media
|
||||||
|
// attached to this status.
|
||||||
|
MediaAttachments []*WebAttachment `json:"media_attachments"`
|
||||||
|
|
||||||
// Template-ready language tag and
|
// Template-ready language tag and
|
||||||
// string, based on *status.Language.
|
// string, based on *status.Language.
|
||||||
LanguageTag *language.Language
|
LanguageTag *language.Language
|
||||||
|
|
|
@ -46,7 +46,7 @@ type AccountTestSuite struct {
|
||||||
func (suite *AccountTestSuite) TestGetAccountStatuses() {
|
func (suite *AccountTestSuite) TestGetAccountStatuses() {
|
||||||
statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", false, false)
|
statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", false, false)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Len(statuses, 7)
|
suite.Len(statuses, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() {
|
func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() {
|
||||||
|
@ -69,7 +69,7 @@ func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.Len(statuses, 1)
|
suite.Len(statuses, 2)
|
||||||
|
|
||||||
// try to get the last page (should be empty)
|
// try to get the last page (should be empty)
|
||||||
statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 3, false, false, statuses[len(statuses)-1].ID, "", false, false)
|
statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 3, false, false, statuses[len(statuses)-1].ID, "", false, false)
|
||||||
|
@ -187,7 +187,7 @@ func (suite *AccountTestSuite) TestGetAccountStatusesExcludeRepliesExcludesSelfR
|
||||||
func (suite *AccountTestSuite) TestGetAccountStatusesMediaOnly() {
|
func (suite *AccountTestSuite) TestGetAccountStatusesMediaOnly() {
|
||||||
statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", true, false)
|
statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", true, false)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Len(statuses, 1)
|
suite.Len(statuses, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountTestSuite) TestGetAccountBy() {
|
func (suite *AccountTestSuite) TestGetAccountBy() {
|
||||||
|
|
|
@ -114,7 +114,7 @@ func (suite *BasicTestSuite) TestGetAllStatuses() {
|
||||||
s := []*gtsmodel.Status{}
|
s := []*gtsmodel.Status{}
|
||||||
err := suite.db.GetAll(context.Background(), &s)
|
err := suite.db.GetAll(context.Background(), &s)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Len(s, 23)
|
suite.Len(s, 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BasicTestSuite) TestGetAllNotNull() {
|
func (suite *BasicTestSuite) TestGetAllNotNull() {
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (suite *InstanceTestSuite) TestCountInstanceUsersRemote() {
|
||||||
func (suite *InstanceTestSuite) TestCountInstanceStatuses() {
|
func (suite *InstanceTestSuite) TestCountInstanceStatuses() {
|
||||||
count, err := suite.db.CountInstanceStatuses(context.Background(), config.GetHost())
|
count, err := suite.db.CountInstanceStatuses(context.Background(), config.GetHost())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(19, count)
|
suite.Equal(20, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() {
|
func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() {
|
||||||
|
|
|
@ -169,12 +169,7 @@ func (suite *StatusTestSuite) TestGetStatusChildren() {
|
||||||
targetStatus := suite.testStatuses["local_account_1_status_1"]
|
targetStatus := suite.testStatuses["local_account_1_status_1"]
|
||||||
children, err := suite.db.GetStatusChildren(context.Background(), targetStatus.ID)
|
children, err := suite.db.GetStatusChildren(context.Background(), targetStatus.ID)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Len(children, 2)
|
suite.Len(children, 3)
|
||||||
for _, c := range children {
|
|
||||||
suite.Equal(targetStatus.URI, c.InReplyToURI)
|
|
||||||
suite.Equal(targetStatus.AccountID, c.InReplyToAccountID)
|
|
||||||
suite.Equal(targetStatus.ID, c.InReplyToID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *StatusTestSuite) TestDeleteStatus() {
|
func (suite *StatusTestSuite) TestDeleteStatus() {
|
||||||
|
|
|
@ -155,7 +155,7 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.checkStatuses(s, id.Highest, id.Lowest, 19)
|
suite.checkStatuses(s, id.Highest, id.Lowest, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
|
func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
|
||||||
|
@ -187,7 +187,7 @@ func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.checkStatuses(s, id.Highest, id.Lowest, 7)
|
suite.checkStatuses(s, id.Highest, id.Lowest, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() {
|
func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() {
|
||||||
|
@ -209,7 +209,7 @@ func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.NotContains(s, futureStatus)
|
suite.NotContains(s, futureStatus)
|
||||||
suite.checkStatuses(s, id.Highest, id.Lowest, 19)
|
suite.checkStatuses(s, id.Highest, id.Lowest, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *TimelineTestSuite) TestGetHomeTimelineBackToFront() {
|
func (suite *TimelineTestSuite) TestGetHomeTimelineBackToFront() {
|
||||||
|
@ -240,8 +240,8 @@ func (suite *TimelineTestSuite) TestGetHomeTimelineFromHighest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
suite.checkStatuses(s, id.Highest, id.Lowest, 5)
|
suite.checkStatuses(s, id.Highest, id.Lowest, 5)
|
||||||
suite.Equal("01HH9KYNQPA416TNJ53NSATP40", s[0].ID)
|
suite.Equal("01J2M1HPFSS54S60Y0KYV23KJE", s[0].ID)
|
||||||
suite.Equal("01G20ZM733MGN8J344T4ZDDFY1", s[len(s)-1].ID)
|
suite.Equal("01G36SF3V6Y6V5BF9P4R7PQG7G", s[len(s)-1].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *TimelineTestSuite) TestGetListTimelineNoParams() {
|
func (suite *TimelineTestSuite) TestGetListTimelineNoParams() {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package media
|
package media
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -198,6 +199,30 @@ func (res *ffprobeResult) ImageMeta() (width int, height int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EmbeddedImageMeta extracts embedded image metadata contained within ffprobe'd media result
|
||||||
|
// streams, should be used for pulling album image (can be animated image) from audio files.
|
||||||
|
func (res *ffprobeResult) EmbeddedImageMeta() (width int, height int, framerate float32, err error) {
|
||||||
|
for _, stream := range res.Streams {
|
||||||
|
if stream.Width > width {
|
||||||
|
width = stream.Width
|
||||||
|
}
|
||||||
|
if stream.Height > height {
|
||||||
|
height = stream.Height
|
||||||
|
}
|
||||||
|
if fr := stream.GetFrameRate(); fr > 0 {
|
||||||
|
if framerate == 0 || fr < framerate {
|
||||||
|
framerate = fr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Need width + height but
|
||||||
|
// no framerate is fine.
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
err = errors.New("invalid image stream(s)")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// VideoMeta extracts video metadata contained within ffprobe'd media result streams.
|
// VideoMeta extracts video metadata contained within ffprobe'd media result streams.
|
||||||
func (res *ffprobeResult) VideoMeta() (width, height int, framerate float32, err error) {
|
func (res *ffprobeResult) VideoMeta() (width, height int, framerate float32, err error) {
|
||||||
for _, stream := range res.Streams {
|
for _, stream := range res.Streams {
|
||||||
|
@ -222,6 +247,7 @@ func (res *ffprobeResult) VideoMeta() (width, height int, framerate float32, err
|
||||||
type ffprobeStream struct {
|
type ffprobeStream struct {
|
||||||
CodecName string `json:"codec_name"`
|
CodecName string `json:"codec_name"`
|
||||||
AvgFrameRate string `json:"avg_frame_rate"`
|
AvgFrameRate string `json:"avg_frame_rate"`
|
||||||
|
RFrameRate string `json:"r_frame_rate"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
// + unused fields.
|
// + unused fields.
|
||||||
|
@ -229,7 +255,7 @@ type ffprobeStream struct {
|
||||||
|
|
||||||
// GetFrameRate calculates float32 framerate value from stream json string.
|
// GetFrameRate calculates float32 framerate value from stream json string.
|
||||||
func (str *ffprobeStream) GetFrameRate() float32 {
|
func (str *ffprobeStream) GetFrameRate() float32 {
|
||||||
if str.AvgFrameRate != "" {
|
numDen := func(strFR string) (float32, float32) {
|
||||||
var (
|
var (
|
||||||
// numerator
|
// numerator
|
||||||
num float32
|
num float32
|
||||||
|
@ -239,7 +265,7 @@ func (str *ffprobeStream) GetFrameRate() float32 {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check for a provided inequality, i.e. numerator / denominator.
|
// Check for a provided inequality, i.e. numerator / denominator.
|
||||||
if p := strings.SplitN(str.AvgFrameRate, "/", 2); len(p) == 2 {
|
if p := strings.SplitN(strFR, "/", 2); len(p) == 2 {
|
||||||
n, _ := strconv.ParseFloat(p[0], 32)
|
n, _ := strconv.ParseFloat(p[0], 32)
|
||||||
d, _ := strconv.ParseFloat(p[1], 32)
|
d, _ := strconv.ParseFloat(p[1], 32)
|
||||||
num, den = float32(n), float32(d)
|
num, den = float32(n), float32(d)
|
||||||
|
@ -248,8 +274,26 @@ func (str *ffprobeStream) GetFrameRate() float32 {
|
||||||
num = float32(n)
|
num = float32(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
return num / den
|
return num, den
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var num, den float32
|
||||||
|
if str.AvgFrameRate != "" {
|
||||||
|
// Check if we have avg_frame_rate.
|
||||||
|
num, den = numDen(str.AvgFrameRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num == 0 && str.RFrameRate != "" {
|
||||||
|
// Check if we have r_frame_rate.
|
||||||
|
num, den = numDen(str.RFrameRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num != 0 {
|
||||||
|
// Found it.
|
||||||
|
// Avoid divide by zero.
|
||||||
|
return num / cmp.Or(den, 1)
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -299,8 +299,14 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
|
||||||
|
|
||||||
// Extract image metadata from streams (if any),
|
// Extract image metadata from streams (if any),
|
||||||
// this will only exist for embedded album art.
|
// this will only exist for embedded album art.
|
||||||
width, height, _ := result.ImageMeta()
|
width, height, framerate, _ := result.EmbeddedImageMeta()
|
||||||
if width > 0 && height > 0 {
|
if width > 0 && height > 0 {
|
||||||
|
// Unlikely to need these but masto API includes them.
|
||||||
|
p.media.FileMeta.Original.Width = width
|
||||||
|
p.media.FileMeta.Original.Height = height
|
||||||
|
if framerate != 0 {
|
||||||
|
p.media.FileMeta.Original.Framerate = &framerate
|
||||||
|
}
|
||||||
|
|
||||||
// Determine thumbnail dimensions to use.
|
// Determine thumbnail dimensions to use.
|
||||||
thumbWidth, thumbHeight := thumbSize(width, height)
|
thumbWidth, thumbHeight := thumbSize(width, height)
|
||||||
|
|
|
@ -41,11 +41,11 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() {
|
||||||
func (suite *GetRSSTestSuite) TestGetAccountRSSZork() {
|
func (suite *GetRSSTestSuite) TestGetAccountRSSZork() {
|
||||||
getFeed, lastModified, err := suite.accountProcessor.GetRSSFeedForUsername(context.Background(), "the_mighty_zork")
|
getFeed, lastModified, err := suite.accountProcessor.GetRSSFeedForUsername(context.Background(), "the_mighty_zork")
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.EqualValues(1702200240, lastModified.Unix())
|
suite.EqualValues(1704878640, lastModified.Unix())
|
||||||
|
|
||||||
feed, err := getFeed()
|
feed, err := getFeed()
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>Posts from @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n <description>Posts from @the_mighty_zork@localhost:8080</description>\n <pubDate>Sun, 10 Dec 2023 09:24:00 +0000</pubDate>\n <lastBuildDate>Sun, 10 Dec 2023 09:24:00 +0000</lastBuildDate>\n <image>\n <url>http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg</url>\n <title>Avatar for @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n </image>\n <item>\n <title>HTML in post</title>\n <link>http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</link>\n <description>@the_mighty_zork@localhost:8080 made a new post: "Here's a bunch of HTML, read it and weep, weep then!

```html
<section class="about-user">
 <div class="col-header">
 <h2>About</h2>
 </div> 
 <div class="fields">
 <h3 class="sr-only">Fields</h3>
 <dl>
...</description>\n <content:encoded><![CDATA[<p>Here's a bunch of HTML, read it and weep, weep then!</p><pre><code class=\"language-html\"><section class="about-user">\n <div class="col-header">\n <h2>About</h2>\n </div> \n <div class="fields">\n <h3 class="sr-only">Fields</h3>\n <dl>\n <div class="field">\n <dt>should you follow me?</dt>\n <dd>maybe!</dd>\n </div>\n <div class="field">\n <dt>age</dt>\n <dd>120</dd>\n </div>\n </dl>\n </div>\n <div class="bio">\n <h3 class="sr-only">Bio</h3>\n <p>i post about things that concern me</p>\n </div>\n <div class="sr-only" role="group">\n <h3 class="sr-only">Stats</h3>\n <span>Joined in Jun, 2022.</span>\n <span>8 posts.</span>\n <span>Followed by 1.</span>\n <span>Following 1.</span>\n </div>\n <div class="accountstats" aria-hidden="true">\n <b>Joined</b><time datetime="2022-06-04T13:12:00.000Z">Jun, 2022</time>\n <b>Posts</b><span>8</span>\n <b>Followed by</b><span>1</span>\n <b>Following</b><span>1</span>\n </div>\n</section>\n</code></pre><p>There, hope you liked that!</p>]]></content:encoded>\n <author>@the_mighty_zork@localhost:8080</author>\n <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</guid>\n <pubDate>Sun, 10 Dec 2023 09:24:00 +0000</pubDate>\n <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n </item>\n <item>\n <title>introduction post</title>\n <link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>\n <description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>\n <content:encoded><![CDATA[hello everyone!]]></content:encoded>\n <author>@the_mighty_zork@localhost:8080</author>\n <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>\n <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n </item>\n </channel>\n</rss>", feed)
|
suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n <channel>\n <title>Posts from @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n <description>Posts from @the_mighty_zork@localhost:8080</description>\n <pubDate>Wed, 10 Jan 2024 09:24:00 +0000</pubDate>\n <lastBuildDate>Wed, 10 Jan 2024 09:24:00 +0000</lastBuildDate>\n <image>\n <url>http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg</url>\n <title>Avatar for @the_mighty_zork@localhost:8080</title>\n <link>http://localhost:8080/@the_mighty_zork</link>\n </image>\n <item>\n <title>HTML in post</title>\n <link>http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</link>\n <description>@the_mighty_zork@localhost:8080 made a new post: "Here's a bunch of HTML, read it and weep, weep then!

```html
<section class="about-user">
 <div class="col-header">
 <h2>About</h2>
 </div> 
 <div class="fields">
 <h3 class="sr-only">Fields</h3>
 <dl>
...</description>\n <content:encoded><![CDATA[<p>Here's a bunch of HTML, read it and weep, weep then!</p><pre><code class=\"language-html\"><section class="about-user">\n <div class="col-header">\n <h2>About</h2>\n </div> \n <div class="fields">\n <h3 class="sr-only">Fields</h3>\n <dl>\n <div class="field">\n <dt>should you follow me?</dt>\n <dd>maybe!</dd>\n </div>\n <div class="field">\n <dt>age</dt>\n <dd>120</dd>\n </div>\n </dl>\n </div>\n <div class="bio">\n <h3 class="sr-only">Bio</h3>\n <p>i post about things that concern me</p>\n </div>\n <div class="sr-only" role="group">\n <h3 class="sr-only">Stats</h3>\n <span>Joined in Jun, 2022.</span>\n <span>8 posts.</span>\n <span>Followed by 1.</span>\n <span>Following 1.</span>\n </div>\n <div class="accountstats" aria-hidden="true">\n <b>Joined</b><time datetime="2022-06-04T13:12:00.000Z">Jun, 2022</time>\n <b>Posts</b><span>8</span>\n <b>Followed by</b><span>1</span>\n <b>Following</b><span>1</span>\n </div>\n</section>\n</code></pre><p>There, hope you liked that!</p>]]></content:encoded>\n <author>@the_mighty_zork@localhost:8080</author>\n <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</guid>\n <pubDate>Sun, 10 Dec 2023 09:24:00 +0000</pubDate>\n <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n </item>\n <item>\n <title>introduction post</title>\n <link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>\n <description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>\n <content:encoded><![CDATA[hello everyone!]]></content:encoded>\n <author>@the_mighty_zork@localhost:8080</author>\n <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>\n <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n </item>\n </channel>\n</rss>", feed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GetRSSTestSuite) TestGetAccountRSSZorkNoPosts() {
|
func (suite *GetRSSTestSuite) TestGetAccountRSSZorkNoPosts() {
|
||||||
|
|
|
@ -228,7 +228,7 @@ func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossible() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.checkStatuses(statuses, id.Highest, id.Lowest, 19)
|
suite.checkStatuses(statuses, id.Highest, id.Lowest, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossiblePageUp() {
|
func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossiblePageUp() {
|
||||||
|
@ -255,7 +255,7 @@ func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossiblePageUp() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.checkStatuses(statuses, id.Highest, id.Lowest, 19)
|
suite.checkStatuses(statuses, id.Highest, id.Lowest, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GetTestSuite) TestGetNewTimelineNoFollowing() {
|
func (suite *GetTestSuite) TestGetNewTimelineNoFollowing() {
|
||||||
|
@ -284,7 +284,7 @@ func (suite *GetTestSuite) TestGetNewTimelineNoFollowing() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
suite.FailNow(err.Error())
|
suite.FailNow(err.Error())
|
||||||
}
|
}
|
||||||
suite.checkStatuses(statuses, id.Highest, id.Lowest, 7)
|
suite.checkStatuses(statuses, id.Highest, id.Lowest, 8)
|
||||||
|
|
||||||
for _, s := range statuses {
|
for _, s := range statuses {
|
||||||
if s.GetAccountID() != testAccount.ID {
|
if s.GetAccountID() != testAccount.ID {
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (suite *PruneTestSuite) TestPrune() {
|
||||||
|
|
||||||
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(18, pruned)
|
suite.Equal(19, pruned)
|
||||||
suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ func (suite *PruneTestSuite) TestPruneTwice() {
|
||||||
|
|
||||||
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(18, pruned)
|
suite.Equal(19, pruned)
|
||||||
suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
||||||
|
|
||||||
// Prune same again, nothing should be pruned this time.
|
// Prune same again, nothing should be pruned this time.
|
||||||
|
@ -78,7 +78,7 @@ func (suite *PruneTestSuite) TestPruneTo0() {
|
||||||
|
|
||||||
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(23, pruned)
|
suite.Equal(24, pruned)
|
||||||
suite.Equal(0, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
suite.Equal(0, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func (suite *PruneTestSuite) TestPruneToInfinityAndBeyond() {
|
||||||
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(0, pruned)
|
suite.Equal(0, pruned)
|
||||||
suite.Equal(23, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
suite.Equal(24, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPruneTestSuite(t *testing.T) {
|
func TestPruneTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -624,7 +624,7 @@ func (c *Converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.M
|
||||||
Y: a.FileMeta.Focus.Y,
|
Y: a.FileMeta.Focus.Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
case gtsmodel.FileTypeVideo:
|
case gtsmodel.FileTypeVideo, gtsmodel.FileTypeAudio:
|
||||||
if i := a.FileMeta.Original.Duration; i != nil {
|
if i := a.FileMeta.Original.Duration; i != nil {
|
||||||
apiAttachment.Meta.Original.Duration = *i
|
apiAttachment.Meta.Original.Duration = *i
|
||||||
}
|
}
|
||||||
|
@ -1062,14 +1062,36 @@ func (c *Converter) StatusToWebStatus(
|
||||||
webStatus.PollOptions = PollOptions
|
webStatus.PollOptions = PollOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark local.
|
||||||
|
webStatus.Local = *s.Local
|
||||||
|
|
||||||
// Set additional templating
|
// Set additional templating
|
||||||
// variables on media attachments.
|
// variables on media attachments.
|
||||||
for _, a := range webStatus.MediaAttachments {
|
|
||||||
a.Sensitive = webStatus.Sensitive
|
// Get gtsmodel attachments
|
||||||
|
// into a convenient map.
|
||||||
|
ogAttachments := make(
|
||||||
|
map[string]*gtsmodel.MediaAttachment,
|
||||||
|
len(s.Attachments),
|
||||||
|
)
|
||||||
|
for _, a := range s.Attachments {
|
||||||
|
ogAttachments[a.ID] = a
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark this as a local status.
|
// Convert each API attachment
|
||||||
webStatus.Local = *s.Local
|
// into a web attachment.
|
||||||
|
webStatus.MediaAttachments = make(
|
||||||
|
[]*apimodel.WebAttachment,
|
||||||
|
len(apiStatus.MediaAttachments),
|
||||||
|
)
|
||||||
|
for i, apiAttachment := range apiStatus.MediaAttachments {
|
||||||
|
ogAttachment := ogAttachments[apiAttachment.ID]
|
||||||
|
webStatus.MediaAttachments[i] = &apimodel.WebAttachment{
|
||||||
|
Attachment: apiAttachment,
|
||||||
|
Sensitive: apiStatus.Sensitive,
|
||||||
|
MIMEType: ogAttachment.File.ContentType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return webStatus, nil
|
return webStatus, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"enable_rss": true,
|
"enable_rss": true,
|
||||||
|
@ -116,8 +116,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendAliasedAndMoved()
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"source": {
|
"source": {
|
||||||
|
@ -209,8 +209,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct()
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [
|
"emojis": [
|
||||||
{
|
{
|
||||||
"shortcode": "rainbow",
|
"shortcode": "rainbow",
|
||||||
|
@ -259,8 +259,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [
|
"emojis": [
|
||||||
{
|
{
|
||||||
"shortcode": "rainbow",
|
"shortcode": "rainbow",
|
||||||
|
@ -305,8 +305,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
|
||||||
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
"header_description": "A very old-school screenshot of the original team fortress mod for quake",
|
||||||
"followers_count": 2,
|
"followers_count": 2,
|
||||||
"following_count": 2,
|
"following_count": 2,
|
||||||
"statuses_count": 7,
|
"statuses_count": 8,
|
||||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
"last_status_at": "2024-01-10T09:24:00.000Z",
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"source": {
|
"source": {
|
||||||
|
@ -943,6 +943,18 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
||||||
"emojis": [],
|
"emojis": [],
|
||||||
"fields": []
|
"fields": []
|
||||||
},
|
},
|
||||||
|
"mentions": [
|
||||||
|
{
|
||||||
|
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||||
|
"username": "admin",
|
||||||
|
"url": "http://localhost:8080/@admin",
|
||||||
|
"acct": "admin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [],
|
||||||
|
"emojis": [],
|
||||||
|
"card": null,
|
||||||
|
"poll": null,
|
||||||
"media_attachments": [
|
"media_attachments": [
|
||||||
{
|
{
|
||||||
"id": "01HE7Y3C432WRSNS10EZM86SA5",
|
"id": "01HE7Y3C432WRSNS10EZM86SA5",
|
||||||
|
@ -971,7 +983,9 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Photograph of a sloth, Public Domain.",
|
"description": "Photograph of a sloth, Public Domain.",
|
||||||
"blurhash": "LNEC{|w}0K9GsEtPM|j[NFbHoeof"
|
"blurhash": "LNEC{|w}0K9GsEtPM|j[NFbHoeof",
|
||||||
|
"Sensitive": true,
|
||||||
|
"MIMEType": "image/jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "01HE7ZFX9GKA5ZZVD4FACABSS9",
|
"id": "01HE7ZFX9GKA5ZZVD4FACABSS9",
|
||||||
|
@ -983,7 +997,9 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
||||||
"preview_remote_url": null,
|
"preview_remote_url": null,
|
||||||
"meta": null,
|
"meta": null,
|
||||||
"description": "SVG line art of a sloth, public domain",
|
"description": "SVG line art of a sloth, public domain",
|
||||||
"blurhash": "L26*j+~qE1RP?wxut7ofRlM{R*of"
|
"blurhash": "L26*j+~qE1RP?wxut7ofRlM{R*of",
|
||||||
|
"Sensitive": true,
|
||||||
|
"MIMEType": "image/svg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "01HE88YG74PVAB81PX2XA9F3FG",
|
"id": "01HE88YG74PVAB81PX2XA9F3FG",
|
||||||
|
@ -995,21 +1011,11 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
||||||
"preview_remote_url": null,
|
"preview_remote_url": null,
|
||||||
"meta": null,
|
"meta": null,
|
||||||
"description": "Jolly salsa song, public domain.",
|
"description": "Jolly salsa song, public domain.",
|
||||||
"blurhash": null
|
"blurhash": null,
|
||||||
|
"Sensitive": true,
|
||||||
|
"MIMEType": "audio/mpeg"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mentions": [
|
|
||||||
{
|
|
||||||
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
|
|
||||||
"username": "admin",
|
|
||||||
"url": "http://localhost:8080/@admin",
|
|
||||||
"acct": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [],
|
|
||||||
"emojis": [],
|
|
||||||
"card": null,
|
|
||||||
"poll": null,
|
|
||||||
"LanguageTag": "en",
|
"LanguageTag": "en",
|
||||||
"PollOptions": null,
|
"PollOptions": null,
|
||||||
"Local": false,
|
"Local": false,
|
||||||
|
@ -1249,7 +1255,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 19,
|
"status_count": 20,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.png",
|
"thumbnail": "http://localhost:8080/assets/logo.png",
|
||||||
|
|
BIN
testrig/media/ghosts-original.mp3
Normal file
BIN
testrig/media/ghosts-original.mp3
Normal file
Binary file not shown.
BIN
testrig/media/ghosts-small.jpg
Normal file
BIN
testrig/media/ghosts-small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 6 KiB |
|
@ -989,6 +989,53 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment {
|
||||||
Header: util.Ptr(true),
|
Header: util.Ptr(true),
|
||||||
Cached: util.Ptr(true),
|
Cached: util.Ptr(true),
|
||||||
},
|
},
|
||||||
|
"local_account_1_status_8_attachment_1": {
|
||||||
|
ID: "01J2M20K6K9XQC4WSB961YJHV6",
|
||||||
|
StatusID: "01J2M1HPFSS54S60Y0KYV23KJE",
|
||||||
|
URL: "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01J2M20K6K9XQC4WSB961YJHV6.mp3",
|
||||||
|
RemoteURL: "",
|
||||||
|
CreatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"),
|
||||||
|
UpdatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"),
|
||||||
|
Type: gtsmodel.FileTypeAudio,
|
||||||
|
FileMeta: gtsmodel.FileMeta{
|
||||||
|
Original: gtsmodel.Original{
|
||||||
|
Width: 500,
|
||||||
|
Height: 500,
|
||||||
|
Size: 0,
|
||||||
|
Aspect: 0,
|
||||||
|
},
|
||||||
|
Small: gtsmodel.Small{
|
||||||
|
Width: 500,
|
||||||
|
Height: 500,
|
||||||
|
Size: 250000,
|
||||||
|
Aspect: 1,
|
||||||
|
},
|
||||||
|
Focus: gtsmodel.Focus{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||||
|
Description: "This is a track from Nine Inch Nail's \"Ghosts I-V\" album. This is the third track from \"Ghosts II\".",
|
||||||
|
ScheduledStatusID: "",
|
||||||
|
Blurhash: "LeDvfpayIUof01j[xuayxuayaxj[",
|
||||||
|
Processing: 2,
|
||||||
|
File: gtsmodel.File{
|
||||||
|
Path: "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01J2M20K6K9XQC4WSB961YJHV6.mp3",
|
||||||
|
ContentType: "audio/mpeg",
|
||||||
|
FileSize: 7483917,
|
||||||
|
},
|
||||||
|
Thumbnail: gtsmodel.Thumbnail{
|
||||||
|
Path: "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01J2M20K6K9XQC4WSB961YJHV6.jpg",
|
||||||
|
ContentType: "image/jpeg",
|
||||||
|
FileSize: 6132,
|
||||||
|
URL: "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01J2M20K6K9XQC4WSB961YJHV6.jpg",
|
||||||
|
RemoteURL: "",
|
||||||
|
},
|
||||||
|
Avatar: util.Ptr(false),
|
||||||
|
Header: util.Ptr(false),
|
||||||
|
Cached: util.Ptr(true),
|
||||||
|
},
|
||||||
"remote_account_1_status_1_attachment_1": {
|
"remote_account_1_status_1_attachment_1": {
|
||||||
ID: "01FVW7RXPQ8YJHTEXYPE7Q8ZY0",
|
ID: "01FVW7RXPQ8YJHTEXYPE7Q8ZY0",
|
||||||
StatusID: "01FVW7JHQFSFK166WWKR8CBA6M",
|
StatusID: "01FVW7JHQFSFK166WWKR8CBA6M",
|
||||||
|
@ -1347,6 +1394,10 @@ func newTestStoredAttachments() map[string]filenames {
|
||||||
Original: "team-fortress-original.jpg",
|
Original: "team-fortress-original.jpg",
|
||||||
Small: "team-fortress-small.jpg",
|
Small: "team-fortress-small.jpg",
|
||||||
},
|
},
|
||||||
|
"local_account_1_status_8_attachment_1": {
|
||||||
|
Original: "ghosts-original.mp3",
|
||||||
|
Small: "ghosts-small.jpg",
|
||||||
|
},
|
||||||
"remote_account_1_status_1_attachment_1": {
|
"remote_account_1_status_1_attachment_1": {
|
||||||
Original: "thoughtsofdog-original.jpg",
|
Original: "thoughtsofdog-original.jpg",
|
||||||
Small: "thoughtsofdog-small.jpg",
|
Small: "thoughtsofdog-small.jpg",
|
||||||
|
@ -1644,6 +1695,31 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
||||||
Federated: util.Ptr(true),
|
Federated: util.Ptr(true),
|
||||||
ActivityStreamsType: ap.ObjectNote,
|
ActivityStreamsType: ap.ObjectNote,
|
||||||
},
|
},
|
||||||
|
"local_account_1_status_8": {
|
||||||
|
ID: "01J2M1HPFSS54S60Y0KYV23KJE",
|
||||||
|
URI: "http://localhost:8080/users/the_mighty_zork/statuses/01J2M1HPFSS54S60Y0KYV23KJE",
|
||||||
|
URL: "http://localhost:8080/@the_mighty_zork/statuses/01J2M1HPFSS54S60Y0KYV23KJE",
|
||||||
|
Content: "<p>Thanks! Here's a NIN track</p>",
|
||||||
|
Text: "Thanks! Here's a NIN track",
|
||||||
|
AttachmentIDs: []string{"01J2M20K6K9XQC4WSB961YJHV6"},
|
||||||
|
CreatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"),
|
||||||
|
UpdatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"),
|
||||||
|
Local: util.Ptr(true),
|
||||||
|
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||||
|
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||||
|
InReplyToID: "01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||||
|
InReplyToAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||||
|
InReplyToURI: "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||||
|
BoostOfID: "",
|
||||||
|
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||||
|
ContentWarning: "",
|
||||||
|
Visibility: gtsmodel.VisibilityPublic,
|
||||||
|
Sensitive: util.Ptr(false),
|
||||||
|
Language: "en",
|
||||||
|
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
|
||||||
|
Federated: util.Ptr(true),
|
||||||
|
ActivityStreamsType: ap.ObjectNote,
|
||||||
|
},
|
||||||
"local_account_2_status_1": {
|
"local_account_2_status_1": {
|
||||||
ID: "01F8MHBQCBTDKN6X5VHGMMN4MA",
|
ID: "01F8MHBQCBTDKN6X5VHGMMN4MA",
|
||||||
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
|
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA",
|
||||||
|
@ -2208,6 +2284,10 @@ func NewTestThreadToStatus() []*gtsmodel.ThreadToStatus {
|
||||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||||
StatusID: "01FCQSQ667XHJ9AV9T27SJJSX5",
|
StatusID: "01FCQSQ667XHJ9AV9T27SJJSX5",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||||
|
StatusID: "01J2M1HPFSS54S60Y0KYV23KJE",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ThreadID: "01HCWE71MGRRDSHBKXFD5DDSWR",
|
ThreadID: "01HCWE71MGRRDSHBKXFD5DDSWR",
|
||||||
StatusID: "01FN3VJGFH10KR7S2PB0GFJZYG",
|
StatusID: "01FN3VJGFH10KR7S2PB0GFJZYG",
|
||||||
|
|
|
@ -336,6 +336,10 @@ main {
|
||||||
grid-area: sensitive;
|
grid-area: sensitive;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
@ -401,10 +405,18 @@ main {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.odd .media-wrapper:first-child, &.double .media-wrapper {
|
&.odd .media-wrapper:first-child,
|
||||||
|
&.double .media-wrapper {
|
||||||
grid-row: span 2;
|
grid-row: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 42rem) {
|
||||||
|
.media-wrapper {
|
||||||
|
grid-column: span 2;
|
||||||
|
grid-row: span 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -36,16 +36,42 @@
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "videoPreview" }}
|
{{- define "videoPreview" }}
|
||||||
<video
|
<img
|
||||||
|
src="{{- .PreviewURL -}}"
|
||||||
|
loading="lazy"
|
||||||
{{- if .Description }}
|
{{- if .Description }}
|
||||||
alt="{{- .Description -}}"
|
alt="{{- .Description -}}"
|
||||||
title="{{- .Description -}}"
|
title="{{- .Description -}}"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
width="{{- .Meta.Original.Width -}}"
|
width="{{- .Meta.Small.Width -}}"
|
||||||
height="{{- .Meta.Original.Height -}}"
|
height="{{- .Meta.Small.Height -}}"
|
||||||
>
|
/>
|
||||||
<source type="video/mp4" src="{{- .URL -}}"/>
|
{{- end }}
|
||||||
</video>
|
|
||||||
|
{{- define "audioPreview" }}
|
||||||
|
{{- if and .PreviewURL .Meta.Small.Width }}
|
||||||
|
<img
|
||||||
|
src="{{- .PreviewURL -}}"
|
||||||
|
loading="lazy"
|
||||||
|
{{- if .Description }}
|
||||||
|
alt="{{- .Description -}}"
|
||||||
|
title="{{- .Description -}}"
|
||||||
|
{{- end }}
|
||||||
|
width="{{- .Meta.Small.Width -}}"
|
||||||
|
height="{{- .Meta.Small.Height -}}"
|
||||||
|
/>
|
||||||
|
{{- else }}
|
||||||
|
<img
|
||||||
|
src="/assets/logo.png"
|
||||||
|
loading="lazy"
|
||||||
|
{{- if .Description }}
|
||||||
|
alt="{{- .Description -}}"
|
||||||
|
title="{{- .Description -}}"
|
||||||
|
{{- end }}
|
||||||
|
width="518"
|
||||||
|
height="460"
|
||||||
|
/>
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- /* Produces something like "1 attachment", "2 attachments", etc */ -}}
|
{{- /* Produces something like "1 attachment", "2 attachments", etc */ -}}
|
||||||
|
@ -77,21 +103,47 @@ media photoswipe-gallery {{ (len .) | oddOrEven }} {{ if eq (len .) 1 }}single{{
|
||||||
{{- include "videoPreview" $media | indent 4 }}
|
{{- include "videoPreview" $media | indent 4 }}
|
||||||
{{- else if eq .Type "image" }}
|
{{- else if eq .Type "image" }}
|
||||||
{{- include "imagePreview" $media | indent 4 }}
|
{{- include "imagePreview" $media | indent 4 }}
|
||||||
|
{{- else if eq .Type "audio" }}
|
||||||
|
{{- include "audioPreview" $media | indent 4 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
</summary>
|
</summary>
|
||||||
{{- if eq .Type "video" }}
|
{{- if eq .Type "video" }}
|
||||||
<video
|
<video
|
||||||
|
preload="none"
|
||||||
class="plyr-video photoswipe-slide"
|
class="plyr-video photoswipe-slide"
|
||||||
controls
|
controls
|
||||||
data-pswp-index="{{- $index -}}"
|
data-pswp-index="{{- $index -}}"
|
||||||
data-pswp-width="{{- $media.Meta.Original.Width -}}px"
|
poster="{{- .PreviewURL -}}"
|
||||||
data-pswp-height="{{- $media.Meta.Original.Height -}}px"
|
data-pswp-width="{{- $media.Meta.Small.Width -}}px"
|
||||||
|
data-pswp-height="{{- $media.Meta.Small.Height -}}px"
|
||||||
{{- if .Description }}
|
{{- if .Description }}
|
||||||
alt="{{- $media.Description -}}"
|
alt="{{- $media.Description -}}"
|
||||||
title="{{- $media.Description -}}"
|
title="{{- $media.Description -}}"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
>
|
>
|
||||||
<source type="video/mp4" src="{{- $media.URL -}}"/>
|
<source type="{{- $media.MIMEType -}}" src="{{- $media.URL -}}"/>
|
||||||
|
</video>
|
||||||
|
{{- else if eq .Type "audio" }}
|
||||||
|
<video
|
||||||
|
preload="none"
|
||||||
|
class="plyr-video photoswipe-slide"
|
||||||
|
controls
|
||||||
|
data-pswp-index="{{- $index -}}"
|
||||||
|
{{- if and $media.PreviewURL $media.Meta.Small.Width }}
|
||||||
|
poster="{{- .PreviewURL -}}"
|
||||||
|
data-pswp-width="{{- $media.Meta.Small.Width -}}px"
|
||||||
|
data-pswp-height="{{- $media.Meta.Small.Height -}}px"
|
||||||
|
{{- else }}
|
||||||
|
poster="/assets/logo.png"
|
||||||
|
width="518px"
|
||||||
|
height="460px"
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Description }}
|
||||||
|
alt="{{- $media.Description -}}"
|
||||||
|
title="{{- $media.Description -}}"
|
||||||
|
{{- end }}
|
||||||
|
>
|
||||||
|
<source type="{{- $media.MIMEType -}}" src="{{- $media.URL -}}"/>
|
||||||
</video>
|
</video>
|
||||||
{{- else if eq .Type "image" }}
|
{{- else if eq .Type "image" }}
|
||||||
<a
|
<a
|
||||||
|
|
Loading…
Reference in a new issue