Python使用FastAPI提供图片缩略图生成接口

使用pillow的thumbnail生成缩略图时,会保持原图的宽高比;使用的opencv的resize则不会

具体代码如下:

#!/usr/bin/env python
import re
import sys
from enum import Enum
from io import BytesIO
from pathlib import Path
from typing import Annotated, Literal, Optional, Tuple, Union# pip install python-multipart fastapi-cdn-host fastapi uvicorn pillow opencv-python
import cv2  # type:ignore[import-untyped]
import fastapi_cdn_host
import numpy as np
import uvicorn
from fastapi import FastAPI, File, HTTPException, Query
from fastapi.responses import RedirectResponse, Response
from PIL import ImageImageSizeType = Annotated[Tuple[int, int], "图片尺寸(宽,高),如:(1080, 720)"]class Picture:default_size = (351, 190)@staticmethoddef generate_thumbnail_pil(img_bytes: bytes, size, fmt: Literal["JPEG", "PNG"], *, verbose=False) -> bytes:with Image.open(BytesIO(img_bytes)) as image:origin_size = image.sizeimage.thumbnail(size)if verbose:print(f"pil[target_{size=}]: {origin_size} -> {image.size}")bio = BytesIO()if fmt == "JPEG":image = image.convert("RGB")image.save(bio, format=fmt)return bio.getvalue()@staticmethoddef generate_thumbnail_opencv(img_bytes: bytes, size, fmt: Literal[".jpeg", ".png"], *, verbose=False) -> bytes:img = cv2.imdecode(np.frombuffer(img_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)resized = cv2.resize(img, size)if verbose:origin_size, converted_size = img.shape[:2][::-1], resized.shape[:2][::-1]print(f"opencv[target_{size=}]: {origin_size} -> {converted_size}")_, img_encode = cv2.imencode(fmt, resized)return img_encode.tobytes()@classmethoddef thumbnail(cls,img: Union[bytes, BytesIO, str, Path],size: Optional[ImageSizeType] = None,keep_scale=False,fmt: Optional[str] = None,*,verbose=False,) -> bytes:"""生成缩略图:param img: 图片二进制或路径:param size: 缩略图的宽、高, 如果为None,则使用类的default_size:param keep_scale: 是否保持宽高比:param fmt: 缩略图格式(jpg或png):param verbose: 调试用参数,是否打印生成的缩略图尺寸Usage::>>> p = Path('a.jpg')>>> thumb = Picture.thumbnail(p.read_bytes(), fmt=p.suffix)>>> isinstance(thumb, bytes)True"""if size is None:size = cls.default_sizeif fmt is None:if isinstance(img, (str, Path)) and str(img).lower().endswith(".png"):fmt = "png"else:fmt = fmt.strip(".").lower()assert fmt in ("jpg", "jpeg", "png"), "Invalid `fmt`: only support png/jpg"if isinstance(img, BytesIO):img = img.getvalue()elif isinstance(img, (str, Path)):img = Path(img).read_bytes()if keep_scale:fmt1: Literal["PNG", "JPEG"] = "PNG" if fmt == "png" else "JPEG"return cls.generate_thumbnail_pil(img, size, fmt=fmt1, verbose=verbose)else:fmt2: Literal[".png", ".jpeg"] = ".png" if fmt == "png" else ".jpeg"return cls.generate_thumbnail_opencv(img, size, fmt=fmt2, verbose=verbose)class ValidationError(HTTPException):def __init__(self, detail: str, status_code=400) -> None:super().__init__(status_code=status_code, detail=detail)app = FastAPI(title="Thumbnail Generator")
fastapi_cdn_host.monkey_patch_for_docs_ui(app)@app.get("/", include_in_schema=False)
async def to_docs():return RedirectResponse("/docs")class FmtEnum(str, Enum):jpg = "JPEG"png = "PNG"class ImageResponse(Response):media_type: Literal["image/jpeg", "image/png"] = "image/jpeg"def __init__(self, content: bytes, status_code=200, **kw) -> None:super().__init__(content=content, status_code=status_code, **kw)@classmethoddef docs_schema(cls) -> dict:example = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x06f..."return {"content": {"image/png": {"example": str(example)},"image/jpeg": {"example": str(example).replace("PNG", "JFIF")},},"description": "返回二进制JPEG/PNG图片.",}class JpegResponse(ImageResponse):media_type = "image/jpeg"class PngResponse(ImageResponse):media_type = "image/png"@app.post("/thumbnail",response_class=ImageResponse,responses={200: ImageResponse.docs_schema()},
)
async def generate_thumbnail(size: Annotated[Union[str, None],Query(max_length=50,description="缩略图尺寸,默认为: {}x{}".format(*Picture.default_size),),] = None,fmt: Annotated[FmtEnum, Query(description="缩略图格式")] = FmtEnum.jpg,uploaded_image: bytes = File(),
) -> ImageResponse:width, height = Picture.default_sizeif size:try:width, height = map(int, re.findall(r"\d+", size))except (ValueError, TypeError):raise ValidationError(f"Invalid size type! Expected: {width}x{height}, Example: 1080x720")img_bytes = Picture.thumbnail(uploaded_image, size=(width, height), fmt=fmt)return PngResponse(img_bytes) if fmt == FmtEnum.png else JpegResponse(img_bytes)def runserver() -> None:port = int(sys.argv[1]) if sys.argv[1:] and sys.argv[1].isdigit() else 8000uvicorn.run(f"{Path(__file__).stem}:app", port=port, reload=True)if __name__ == "__main__":runserver()

效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/535972.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【力扣】78.子集(回溯)

这道题主要是用的回溯的方法来做的,我是参考的代码回想录里面的思路来写的。刚开始的话我致力于一定要把这个完整的过程推一遍,不推到底我不死心,感觉用掉了自己很多的时间,但是也算是弄懂了吧,就是弄透还是需要一点时…

网络编程:网络编程基础

一、网络发展 1.TCP/IP两个协议阶段 TCP/IP协议已分成了两个不同的协议: 用来检测网络传输中差错的传输控制协议TCP 专门负责对不同网络进行2互联的互联网协议IP 2.网络体系结构 OSI体系口诀:物链网输会示用 2.1网络体系结构概念 每一层都有自己独…

【数据结构取经之路】快速排序的非递归实现

概述 递归实现快速排序在一些场景下有栈溢出的风险,下面就谈谈如何用非递归的方法实现快速排序。 非递归实现的思想 递归实现与非递归实现快速排序的本质是一致的,效率并不会因为用了非递归实现而有所提升。递归实现快速排序的本质就在于通过递归&…

IP数据报格式

每一行都由32位比特,即4个字节组成,每个格子称为字段或者域。IP数据报由20字节的固定部分和最大40字节的可变部分组成。 总长度 总长度为16个比特,该字段的取值以字节为单位,用来表示IPv4数据报的长度(首部长度数据载荷长度)最大…

批量提取PDF指定区域内容到 Excel 以及根据PDF里面第一页的标题来批量重命名-附思路和代码实现

首先说明下,PDF需要是电子版本的,不能是图片或者无法选中的那种。 需求1:假如我有一批数量比较多的同样格式的PDF电子文档,需要把特定多个区域的数字或者文字提取出来 需求2:我有一批PDF文档,但是文件的名…

springboot269反欺诈平台的建设

反欺诈平台设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装反欺诈平台软件来发挥其高效地信息处…

西门子PLCS7-1200位逻辑指令的使用

1.LAD触点 常开触点的位值为1时,常开触点将闭合(ON)。位值为0时,常开触点将闭合(OFF)。 常闭触点的位值为1时,常闭触点将闭合(OFF)。位值为0时,常闭触点将闭…

产品测试方案:视频接入平台并发性能测试方案和报告(即150路视频并发流媒体服务器模块的性能测试方案和报告)

目 录 一、测试目的: 二、测试方案: 2.1、测试思路 2.2、拓扑图 三、测试环境 3.1 服务器配置 3.2 网络摄像机列表 3.3 测试软件 四、测试流程 4.1 H.264并发测试: 4.1.1老版本srsout3.10并发测试 4.1.2 新版本srsout…

【你也能从零基础学会网站开发】Web建站之javascript入门篇 History对象与Location对象

🚀 个人主页 极客小俊 ✍🏻 作者简介:程序猿、设计师、技术分享 🐋 希望大家多多支持, 我们一起学习和进步! 🏅 欢迎评论 ❤️点赞💬评论 📂收藏 📂加关注 History历史对…

IO复用之select

目录 一.select方法介绍 2.1 select 系统调用的原型 2.2 集合的数据结构 2.2.1 fd_set 结构如下: 2.2.2 关于集合fd_set的解析 2.3 select第一个参数 2.4 select方法之超时时间timeout 2.5 select方法的用法简述及返回值 2.6 如何检测集合中有哪些描述符有事件就绪 三…

Xterminal:未来的终端体验

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 开发环境篇 ✨特色专栏: M…

【前端】HTML常用标签

因为想当个全栈,所以巩固了一下HTML与CSS和JS基础,这一篇博客是HTML部分 文章目录 HTML 基础标签 1HTML 基础框架HTML 基础标签语义标签文本格式化标签div 与 span 标签图像标签超链接特殊字符 基础标签 2 | 表格表格的使用表格标签表格属性表格的头部与…