从FastAPI中的在线视频URL返回文件/流响应

问题描述 投票:0回答:2

我正在使用 FastAPI 从

googlevideo.com
返回视频响应。这是我正在使用的代码:

@app.get(params.api_video_route)
async def get_api_video(url=None):

  def iter():
     req = urllib.request.Request(url)

     with urllib.request.urlopen(req) as resp:
         yield from io.BytesIO(resp.read())


  return StreamingResponse(iter(), media_type="video/mp4")

但这不起作用

我希望将这个

Nodejs
转换为Python FastAPI:

app.get("/download-video", function(req, res) { 
 http.get(decodeURIComponent(req.query.url), function(response) { 
   res.setHeader("Content-Length", response.headers["content-length"]); 
   if (response.statusCode >= 400)         
     res.status(500).send("Error");                     
     response.on("data", function(chunk) { res.write(chunk); }); 
     response.on("end", function() { res.end(); }); }); });
python video-streaming urllib fastapi streamingresponsebody
2个回答
2
投票

快速解决方案是将

yield from io.BytesIO(resp.read())
替换为以下内容(请参阅 FastAPI 文档 -
StreamingResponse
了解更多详细信息)。

 yield from resp

但是,我建议使用

urllib.request
 库,而不是使用 resp.read()
HTTPX
(这会将整个文件内容读入内存,因此需要很长时间才能响应),与
urllib
requests
库相比,提供
async
也支持
,更适合FastAPI等
async
环境。此外,它还支持Streaming Responses(也请参阅
async
Streaming Responses
),因此,您可以避免一次将整个响应正文加载到内存中(特别是在处理大文件时)。下面以同步和异步方式提供了有关如何从给定 URL 流式传输视频的示例。

注意:下面的两个版本都允许多个客户端连接到服务器并获取视频流而不会被阻塞,因为 FastAPI 中的正常

def
端点是在外部线程池中运行,然后等待,而不是直接调用(因为它会阻塞服务器)——从而确保 FastAPI 仍然异步工作。即使您使用 async def
 定义了下面第一个示例的端点,它仍然不会阻塞服务器,因为 
StreamingResponse
 将在外部线程池中运行代码(用于发送正文块),然后等待(有查看 
this comment 和源代码 here),如果用于流式传输响应正文的函数(即 iterfile()
在下面的示例中)是一个普通的生成器/迭代器(如第一个示例),而不是一个 
async
 生成器/迭代器(如第二个示例)。但是,如果您在该端点内有一些其他 I/O 或 CPU 阻塞操作,则会导致服务器阻塞,因此,您应该删除该端点上的 
async
 定义。第二个示例演示了如何在 
async def
 端点中实现视频流,当您必须调用端点内的其他 
async
 函数时,这非常有用,并且您可以避免 FastAPI 运行外部线程池中的端点。有关 
await
def
 的更多详细信息,请查看 
这个答案
以下示例分别使用 async def

iter_bytes()

 方法来分块获取响应正文。这些函数(如上面的文档链接和源代码
此处
中所述)可以处理 gzip、deflate 和 brotli 编码的响应。您也可以使用 
aiter_bytes() 方法来获取原始响应字节,而不应用内容解码(如果不需要)。与
iter_raw()
相比,此方法允许您选择定义用于流式传输响应内容的
iter_bytes()
,例如 
chunk_size
。但是,这并不意味着您直接从服务器(正在提供文件服务)中以该大小的块读取正文。如果您仔细查看 
iter_raw(1024 * 1024)
的源代码,您会发现它只是使用
iter_raw()
将字节内容存储到内存中(使用
ByteChunker
流)并以固定格式返回内容-size chunks,取决于传递给函数的块大小(而
BytesIO()
,如上面链接的源代码所示,包含从流中读取的实际字节块)。
使用 
raw_stream_bytes

HTTPX
 端点
def

使用
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import httpx

app = FastAPI()

@app.get('/video')
def get_video(url: str):

    def iterfile():
        with httpx.stream("GET", url) as r:
            for chunk in r.iter_bytes():
                yield chunk

    return StreamingResponse(iterfile(), media_type="video/mp4")

HTTPX
 端点
async def

您可以使用
此处
提供的公共视频来测试上述内容。示例:

from fastapi import FastAPI from fastapi.responses import StreamingResponse import httpx app = FastAPI() @app.get('/video') async def get_video(url: str): async def iterfile(): async with httpx.AsyncClient() as client: async with client.stream("GET", url) as r: async for chunk in r.aiter_bytes(): yield chunk return StreamingResponse(iterfile(), media_type="video/mp4")

如果您想返回自定义的
http://127.0.0.1:8000/video?url=http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4

Response

,在处理大型视频文件时我真的不建议这样做,因为您应该将整个文件内容读入内存,或者将内容保存到您稍后必须将其再次完全读入内存(但在 
FileResponse
 的情况下,内容将以块的形式读取,如在
实现
FileResponse
,块大小设置为64KB),以便将其发送回客户端 - 请查看
这个答案
这个答案

我遇到了类似的问题,但全部解决了。主要思想是使用 requests.Session() 创建一个会话,并逐一生成一个块,而不是获取所有内容并立即生成它。这非常有效,根本不会产生任何内存问题。

1
投票
FileResponse


© www.soinside.com 2019 - 2024. All rights reserved.