SocialCG/ActivityPub/MediaUpload
The Media Upload mechanism defines a protocol for ActivityPub servers to support uploading document types to be referenced in activites, such as images, video or other binary data. To accomplish this, a client MUST submit a multipart/form-data
message to the user's uploadMedia
endpoint on their ActivityStreams profile object. (Unlike most client to server interactions, using this endpoint does not involve submitting to the outbox
). A client should expect that it must be properly authenticated in order to be able to upload media.
The uploadMedia
endpoint is part of the ActivityPub endpoints
mapping. See ActivityPub extensions.
The submitted form data should contain two parts / fields:
- file
- The media file file being uploaded.
- object
- A shell of an ActivityStreams object, which will be finalized by the server.
Assuming that the server accepts the request and that the user was appropriately authorized to upload media, servers MUST respond with a 201 Created
if the object is immediately available or a 202 Accepted
if the server is still processing the submitted media. The response MUST contain a Location
header pointing to the new or to-be-created object's id
.
The server, having done any appropriate processing on the received file
and putting it in place, transforms the object
that will be retrievable by the id
. In particular, servers MUST append an id
property to the object, and SHOULD include the uploaded and/or processed file paths in the object's url
property. The server MAY wrap the shell object submitted by the user in a Create
via [[#object-without-create|]] if appropriate and present this as the object pointed to by the forementioned Location
header in the post-media-upload response.
Ben Bitdiddle just recorded a video of his friend's latest invention and wants to upload it to his ActivityPub-compatible social network to show it off. He wants to post a Create
of a new Video
object, but first he has to upload the video somewhere. Luckily the ActivityPub instance he is on supports the uploadMedia
endpoint! In his client, he selects the video for upload and adds a title and a description and presses submit.
Ben's client takes a look at Ben's actor profile and finds the uploadMedia
endpoint there.
{"@context": "https://www.w3.org/ns/activitystreams", "type": "Person", "id": "https://chatty.example/ben/", "name": "Ben Bitdiddle", "preferredUsername": "ben", "summary": "Constantly fidgeting with ones and zeroes.", "inbox": "https://chatty.example/ben/inbox/", "outbox": "https://chatty.example/ben/collections/outbox/", "followers": "https://chatty.example/ben/collections/followers/", "following": "https://chatty.example/ben/collections/following/", "liked": "https://chatty.example/ben/collections/liked/", "endpoints": { "id": "https://chatty.example/ben/#endpoints", "uploadMedia": "https://chatty.example/ben/endpoints/uploadMedia/"}}
Ben's client then constructs a shell ActivityPub object (this doesn't have the url for the video itself yet, but that's okay, because that will be added by the server) and submits it (as the object
) along with the video (as the file
) by POST
'ing to the uploadMedia
endpoint.
POST /ben/endpoints/uploadMedia/ HTTP/1.1 Host: chatty.example Content-Type: multipart/form-data; boundary=bfe40faf4ef Authorization: Bearer XXXXXXXXXXX --bfe40faf4ef Content-Disposition: form-data; name="object" Content-Type: application/ld+json; profile="https://www.w3.org/ns/activitystreams" {"@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "actor": "https://chatty.example/ben/", "to": ["https://chatty.example/ben/collections/followers/", "https://www.w3.org/ns/activitystreams#Public"], "object": { "type": "Video", "name": "A wireworld computer!", "attributedTo": "https://chatty.example/ben/", "to": ["https://chatty.example/ben/collections/followers/", "https://www.w3.org/ns/activitystreams#Public"], "summary": "<p>My friend Conway and I went over to our friend Silverman's place and saw the wireworld computer he's been constructing. Pretty crazy!</p> <p>Tomorrow we're going over to our friend Langton's place and see that ant farm he's been working on...</p>"}} --bfe40faf4ef Content-Disposition: form-data; name="file"; filename="wireworld.webm" Content-Type: video/webm ... (binary data) ... --bfe40faf4ef
Ben's client receives an HTTP response from the server with the status code 202 Accepted
since the video is still being transcoded. The response contains a Location
header pointing at the url https://chatty.example/ben/posts/d4152a7c/
. Sure enough, a few minutes later Ben's video shows up in his outbox
with the rest of the fields filled in:
{"@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "id": "https://chatty.example/ben/posts/d4152a7c/", "actor": "https://chatty.example/ben/", "to": ["https://chatty.example/ben/collections/followers/", "https://www.w3.org/ns/activitystreams#Public"], "object": { "type": "Video", "id": "https://chatty.example/ben/posts/6e69f564/", "url": [ {"type": "Link", "href": "https://chatty.example/media/6e69f564/wireworld-small.webm", "mediaType": "video/webm; codecs=\"vp8, vorbis\"", "name": "A wireworld computer! (small)", "width": 640, "height": 480}, {"type": "Link", "href": "https://chatty.example/media/6e69f564/wireworld-large.webm", "mediaType": "video/webm; codecs=\"vp8, vorbis\"", "name": "A wireworld computer! (large)", "width": 1280, "height": 720}, "name": "A wireworld computer!", "attributedTo": "https://chatty.example/ben/", "to": ["https://chatty.example/ben/collections/followers/", "https://www.w3.org/ns/activitystreams#Public"], "summary": "<p>My friend Conway and I went over to our friend Silverman's place and saw the wireworld computer he's been constructing. Pretty crazy!</p> <p>Tomorrow we're going over to our friend Langton's place and see that ant farm he's been working on...</p>"}}