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.mzph.cn/news/742274.shtml

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

相关文章

汇编课设——秒表2

1. 设计要求 基于 51 开发板,利用键盘作为按键输入,将数码管作为显示输出,实现电子秒表。 功能要求: (1)计时精度达到百分之一秒; (2)能按键记录下5次时间并通过按键回看 (3)设置时间,实现倒计时,时间到,数码管闪烁 10 次,并激发蜂鸣器,可通过按键解除。 2. 设计思…

思科网络中如何进行动态NAT配置

一、什么是动态NAT?动态NAT与静态NAT的区别是什么? (1)动态NAT(Network Address Translation)是一种网络地址转换技术,它会动态地将内部私有网络中的局域网IP地址映射为公共IP地址,…

Hack The Box-Codify

目录 信息收集 rustscan nmap dirsearch WEB 提权 get user get root 信息收集 rustscan ┌──(root㉿ru)-[~/kali/hackthebox] └─# rustscan -b 2250 10.10.11.239 --range0-65535 --ulimit4500 -- -A -sC .----. .-. .-. .----..---. .----. .---. .--. .-. …

JVM 类的加载篇

我们都知道一个类从加载到卸载一共分为七个过程 加载 - 链接(验证 - 准备 - 解析) - 初始化 - 使用 - 卸载 下文我们将详细解析这些过程 谁需要加载? 在Java中数据类型分为基本数据类型和引用数据类型,基本数据类型由虚拟机预定义,引用数据类型则需要类的加载 1.加载/装载(loa…

Docker入门二(应用部署、迁移与备份)

文章目录 一、应用部署1.MySQL部署2.Redis部署3.Nginx部署 二、迁移与备份1.容器做成镜像2.把镜像被分成压缩包 一、应用部署 1.MySQL部署 在dokcer中部署mysql,以后不需要在宿主机上装mysql1.做端口映射docker run -id --namemysql5.7 -p 3306:3306 -e MYSQL_ROOT…

网工内推 | 国企、上市公司网工、运维,CCNA即可,补贴福利多

01 深圳新思 招聘岗位:网络工程师(中电集团) 职责描述: 1:负责办公室电脑的桌面运维,主要是windows维护与应用维护; 2:负责办公室网络设备配置,如防火墙,交换…

CMake 编译 raylib 程序

CMakeLists.txt 内容如下: cmake_minimum_required(VERSION 3.0) project(t001) # 搜索指定目录下源文件 file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) # 包含头文件路径 include_directories(F:/vclib/raylib-5.0_win64_mingw-w64/include) # 包含静态…

CountDownLatch介绍和使用

1. CountDownLatch是什么 CountDownLatch 是 Java.util.concurrent 包中的一个同步工具类,用于控制线程的执行顺序。它的主要作用是让一个或多个线程等待其他线程完成操作后再继续执行。 2. CountDownLatch 类常用方法 CountDownLatch(int count) 是 CountDownLa…

软件测试基础概念

一、需求 定义:满足用户期望或正式规定文档所需条件和技能,包含用户需求 用户需求:用户使用产品所必须完成的任务 该需求比较简略 软件需求:详细描述开发人员必须实现的软件功能 需求是标准,测试人员按照这个标准测…

全球首个 AI 超级工程师:拥有全栈技能,一个指令就能完成整个开发过程

全球首位AI软件工程师Devin是由初创公司Cognition推出的,它被认为是世界上第一个完全自主的AI软件工程师[2][15]。Devin具备强大的编程和软件开发能力,能够在多个方面协助或完全独立地完成软件开发任务[15]。它的核心能力包括自学新语言、开发迭代App、自…

C语言分析基础排序算法——归并排序

目录 归并排序 递归版本 非递归版本 非递归版本的问题 归并排序小优化 归并排序 归并排序,分为分治以及合并,分治部分可以使用递归或者非递归完成,归并排序的基本思路是:将已有序的子序列合并,得到完全有序的序列…

社区维修平台|基于SpringBoot+ Mysql+Java+JSP技术的社区维修平台设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 住户后台功能 维修员前台功能 维修员后台功能 管理员功能登录 系统功能设计 数据库E…

C++面试题和笔试题(四)

一、intx[6][4],(*p)[4];px;则*(p2)指向哪里? A X[0][1]B X[0][2]C X[1][0]D X[2][0] 官方解释: D int x[6][4], (*p)[4]; p x; 在这里,x 是一个二维数组,它有6行和4列。p 是一个指向具有4个整数的数组的指针。 当你执行…

DevOps-SonarQube整合Jenkins

下载SonarQube Scanner 登录Jenkins服务器,下载SonarQube Scanner wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip安装unzip,需要通过它来解压zip压缩包 yum install -y unzip解压So…

基于Web的论文管理系统设计

目 录 目 录 III 摘 要 V 关键词 V Abstract VI Key Word VI 第一章 绪论 6 1.1系统设计背景 1 1.2系统设计目的与意义 1 1.3国内外现状 2 1.4本文结构 3 第二章 需求分析 3 2.1系统需求分析 4 2.2系统角色设计 4 第三章 系统开发技术 4 3.1 PHP语言简介和特点 5 3.2 Mysql数据…

【项目笔记】java微服务:黑马头条(day02)

文章目录 app端文章查看,静态化freemarker,分布式文件系统minIO1)文章列表加载1.1)需求分析1.2)表结构分析1.3)导入文章数据库1.3.1)导入数据库1.3.2)导入对应的实体类 1.4)实现思路1.5)接口定义1.6)功能实现1.6.1):导入heima-leadnews-article微服务&am…

数据库管理-第160期 Oracle Vector DB AI-11(20240312)

数据库管理160期 2024-03-12 数据库管理-第160期 Oracle Vector DB & AI-11(20240312)1 向量的函数操作to_vector()将vector转换为标准值vector_norm()vector_dimension_count()vector_dimension_format() 2 将向量转换为字符串或CLOBvector_seriali…

明明jar包存在却报错找不到包名?两招教你解决java: 程序包org.springframework.context.annotation不存在问题!

一、问题提出 IDEA项目有时因为依赖库的问题出现出错: java: 程序包org.springframework.context.annotation不存在,如下图。 二、解决办法 方案1: 重新导入项目 ① 将项目中 .idea .iml 全部删除,项目重新导入 ② 用idea重新…

看完让你的RSA提升一个台阶 [GKCTF 2021]RRRRsa

阅读须知: 探索者安全团队技术文章仅供参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作,由于传播、利用本公众号所提供的技术和信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任,如有侵权烦请告知,我们会立即删除…

2024计算机二级Python

1. 栈是先进先出,队是后进后出 2. 代码输出长度为5并不是\不占用位置,而是\与其后边的数字共同占用一个字符 3. 首先要弄清range函数此时表示的范围是前闭后开,不包含后面的数字,%函数表示的是余数,只有4是被整除的…