1

When I try to upload a video file to a Facebook page using the Graph API in python with this function:

def upload_video_file(page_id: str, access_token: str, video_file: UploadFile):
    upload_url = f"https://graph.facebook.com/{page_id}/videos"
    headers = {"Authorization": f"Bearer {access_token}"}
    files = {"file": video_file.file}
    response = requests.post(upload_url, headers=headers, files=files)
    data = response.json()
    if data:
        return data
    else:
        return {"message": "failed uploud video"}

and execute the above function from within the following FastAPI endpoint:

@router.post("/upload-video/{page_id}")
async def post_video(page_id: str, video_file: UploadFile = File(...), access_token: str = Header(..., description="Access Token")):
    response = upload_video_file(page_id, access_token, video_file)
    return JSONResponse (response)pe here

I get this error:

{
  "error": {
    "message": "The video file you selected is in a format that we don't support.",
    "type": "OAuthException",
    "code": 352,
    "error_subcode": 1363024,
    "is_transient": false,
    "error_user_title": "Format Video Tidak Didukung",
    "error_user_msg": "Format video yang Anda coba unggah tidak didukung. Silakan coba lagi dengan sebuah video dalam format yang didukung.",
    "fbtrace_id": "AZNNyQhyPDfi5AhDOBpdA5c"
  }
}

Does anyone know how to fix this?

Chris
  • 18,724
  • 6
  • 46
  • 80
  • Stack Overflow is an English-only website. Please [edit] the question and translate the error messages into English. – Squarish Jun 08 '23 at 01:12
  • @Squarish `error.message` contains the same information in English as `error.error_user_msg`. – joanis Jun 08 '23 at 01:27
  • 1
    @DimasSurya The message "The video file you selected is in a format that we don't support." probably answers your question. Have you tried with different formats? What format are you using? – joanis Jun 08 '23 at 01:31
  • @joanis i try with mp4 video format – Dimas Surya Jun 08 '23 at 01:33

1 Answers1

1

The error is caused due to sending binary data, using the SpooledTemporaryFile object that Starlette's UploadFile object uses under the hood—please have a look at this answer and this answer for more details and examples—without specifying a filename and/or content-type.

Hence, the solution would be to specify those two attributes when defining the files variable before sending the HTTP request. You can find the relevant requests documentation here and here (see the files argument). You might find this answer helpful as well. Example:

files = {'file': ('video.mp4', video_file.file, 'video/mp4')}

or, if you would like to use the ones that come with the file uploaded by the user, you could instead use the below (make sure they are not None):

files = {'file': (video_file.filename, video_file.file,  video_file.content_type)}


On a side note, I would not suggest using the requests library for performing HTTP requests in an async environment such as FastAPI's. If you would still like to use requests, you should at least drop the async definition from your endpoint, which would result in FastAPI running that endpoint in an external threadpool that would be then awaited, in order to prevent the endpoing from blocking the event loop (and hence, the entire server). Please have a look at this answer for a thorough explanation, details and examples around async def and normal def in FastAPI.

Alternatively, you could use the httpx library, which provides an async API as well, and has very similar syntax to requests. Details and examples can be found here, as well as here and here. The relevant documentation on how to explicitly set the filename and content-type for files, can be found here. Not only that, you could initialise a global Client object at startup and reuse it accross the application, instead of creating a new Client session every time a request arrives to that endpoint. Finally, you could also return a custom JSONResponse to the user when the file failed to upload for some reason, by specifying a custom response status_code—see this answer for more details.

Working Example

from fastapi import FastAPI, Request, File, UploadFile, Header, status
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
import httpx


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Initialise the Client on startup and add it to the state
    async with httpx.AsyncClient() as client:
        yield {'client': client}
        # The Client closes on shutdown 


app = FastAPI(lifespan=lifespan)


@app.post('/upload-video/{page_id}')
async def upload_video(
    request: Request,
    page_id: str,
    file: UploadFile = File(...),
    access_token: str = Header(...),
):
    client = request.state.client
    url = f'https://graph.facebook.com/{page_id}/videos'
    files = {'file': (file.filename, file.file, file.content_type)}
    headers = {'Authorization': f'Bearer {access_token}'}
    req = client.build_request(method='POST', url=url, files=files, headers=headers)
    r = await client.send(req)
    if r.status_code == 200:
        return r.json()
    else:
        return JSONResponse(
            content='File failed to upload',
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        )
Chris
  • 18,724
  • 6
  • 46
  • 80