我正在使用 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(); }); }); });
快速解决方案是将
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
此处提供的公共视频来测试上述内容。示例:如果您想返回自定义的
http://127.0.0.1:8000/video?url=http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
或 Response
,在处理大型视频文件时我真的不建议这样做,因为您应该将整个文件内容读入内存,或者将内容保存到您稍后必须将其再次完全读入内存(但在
FileResponse
的情况下,内容将以块的形式读取,如在实现
FileResponse
,块大小设置为64KB),以便将其发送回客户端 - 请查看这个答案和这个答案。 我遇到了类似的问题,但全部解决了。主要思想是使用 requests.Session() 创建一个会话,并逐一生成一个块,而不是获取所有内容并立即生成它。这非常有效,根本不会产生任何内存问题。
FileResponse