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

news2024/11/20 13:23:01

使用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 Image

ImageSizeType = Annotated[Tuple[int, int], "图片尺寸(宽,高),如:(1080, 720)"]


class Picture:
    default_size = (351, 190)

    @staticmethod
    def 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.size
            image.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()

    @staticmethod
    def 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()

    @classmethod
    def 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_size
        if 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)

    @classmethod
    def 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_size
    if 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 8000
    uvicorn.run(f"{Path(__file__).stem}:app", port=port, reload=True)


if __name__ == "__main__":
    runserver()

效果如下:

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

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

相关文章

蓝桥杯 2022 dp 背包

蓝桥杯 2022 dp 背包 题目链接&#xff1a; https://www.lanqiao.cn/problems/2186/learning/?subject_code1&group_code4&match_num13&match_flow2&origincup 题目&#xff1a; 代码&#xff1a; #include<bits/stdc.h> using namespace std;#defi…

王道机试C++第6章 数学问题和22年蓝桥杯省赛选择题Day34

6.1 进制转换 二进制数&#xff08;十转二&#xff09; 习题描述 大家都知道&#xff0c;数据在计算机里中存储是以二进制的形式存储的。 有一天&#xff0c;小明学了C语言之后&#xff0c;他想知道一个类型为unsigned int 类型的数字&#xff0c;存储在计算机中的二进制串是…

多数问题求解之蒙特卡洛与分治法

多数问题&#xff08;Majority Problem&#xff09;是一个有多种求解方法的经典问题&#xff0c;其问题定义如下&#xff1a; 给定一个大小为 n n n的数组&#xff0c;找出其中出现次数超过 n / 2 n/2 n/2的元素 例如&#xff1a;当输入数组为 [ 5 , 3 , 5 , 2 , 3 , 5 , 5 ] […

JavaEE:网络编程

网络编程&#xff1a;通过代码完成基于网络的跨主机通信 跨主机通信方式&#xff1a; 1.TCP/IP网络 2.蓝牙通信 3.近场通信NFC 4.毫米波通信&#xff1a;功率高&#xff0c;带宽高&#xff0c;抗干扰能力差 其中TCP/IP网络是日常编程中最常涉及到的&#xff0c;最通用的跨主机通…

深度学习进阶:揭秘强化学习原理,实战应用全解析!

作为机器学习领域的一大分支&#xff0c;强化学习以其独特的学习方式吸引了众多研究者和实践者的目光。强化学习&#xff0c;顾名思义&#xff0c;是通过不断地强化与环境的交互来优化决策策略。在这个过程中&#xff0c;智能体通过试错&#xff0c;根据环境给出的奖励信号来调…

Linux:导出环境变量命令export

相关阅读 Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 Linux中的内建命令export命令用于创建一个环境变量&#xff0c;或将一个普通变量导出为环境变量&#xff0c;并且在这个过程中&#xff0c;可以给该环境变量赋值。 下面…

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

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

反无人机电子护栏:原理、算法及简单实现

随着无人机技术的快速发展&#xff0c;其在航拍、农业、物流等领域的应用日益广泛。然而&#xff0c;无人机的不规范使用也带来了安全隐患&#xff0c;如侵犯隐私、干扰航空秩序等。为了有效管理无人机&#xff0c;反无人机电子护栏技术应运而生。 目录 一、反无人机电子护栏…

代码随想录算法训练营Day46 ||leetCode 139.单词拆分 || 322. 零钱兑换 || 279.完全平方数

139.单词拆分 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());vector<bool> dp(s.size() 1, false);dp[0] true;for (int i 1; i < s.size(); …

【Linux】-Linux下的软件商店yum工具介绍(linux和windows互传文件仅仅一个拖拽搞定!!!!)

目录 1.Linux 软件包管理器yum 1.1快速认识yum 1.2 yumz下载方式&#xff08;如何使用yum进行下载&#xff0c;注意下载一定要是root用户或者白名单用户&#xff08;可提权&#xff09;&#xff09; 1.2.1下载小工具rzsz 1.2.2 rzsz使用 1.2.2查看软件包 1.3软件的卸载 2.yum生…

Grapher教程—重建长江中下游降雨量时间变化序列

各位朋友好&#xff01;非常激动&#xff01;新学了一个科研绘图软件&#xff0c;它的大名叫“Grapher”&#xff0c;也许在科研界早已如雷贯耳&#xff0c;但在我这&#xff0c;还得是第一次遇见你&#xff01;来看看小编在老师的指导下鼓捣了三节课搞出来的图。 就问大家&…

6.Java并发编程—深入剖析Java Executors:探索创建线程的5种神奇方式

Executors快速创建线程池的方法 Java通过Executors 工厂提供了5种创建线程池的方法&#xff0c;具体方法如下 方法名描述newSingleThreadExecutor()创建一个单线程的线程池&#xff0c;该线程池中只有一个工作线程。所有任务按照提交的顺序依次执行&#xff0c;保证任务的顺序性…

Mybatis-Plus实现常规增删改操作

文章目录 3.1 MP实现插入操作3.1.1 BaseMapper定义操作方法3.1.2 代码测试 3.2 MP主键字段注解-TableId3.2.1 注解TableId介绍3.2.2 MP主键生成策略介绍3.2.3 MP常用主键生成策略3.2.4 雪花算法(了解) 3.3 普通列注解-TableField3.3.1 注解TableField作用3.3.2 代码示例 3.4.MP…

自适应差分进化算法(SaDE)和差分进化算法(DE)优化BP神经网络

自适应差分进化算法(SaDE)和差分进化算法(DE)优化BP神经网络 自适应差分进化算法(SaDE)和差分进化算法(DE)可以用于优化神经网络中的参数&#xff0c;包括神经网络的权重和偏置。在优化BP神经网络中&#xff0c;DE和SaDE可以帮助找到更好的权重和偏置的组合&#xff0c;以提高…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的交通标志识别系统详解(深度学习模型+UI界面代码+训练数据集)

摘要&#xff1a;本篇博客详细介绍了利用深度学习构建交通标志识别系统的过程&#xff0c;并提供了完整的实现代码。该系统采用了先进的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了性能评估对比&#xff0c;分析了性能指标如mAP、F1 Score等。文章深入探…

4、设计模式之建造者模式(Builder)

一、什么是建造者模式 建造者模式是一种创建型设计模式&#xff0c;也叫生成器模式。 定义&#xff1a;封装一个复杂对象构造过程&#xff0c;并允许按步骤构造。 解释&#xff1a;就是将复杂对象的创建过程拆分成多个简单对象的创建过程&#xff0c;并将这些简单对象组合起来…

吴恩达机器学习-可选实验室:逻辑回归,决策边界(Logistic Regression,Decision Boundary))

文章目录 目标数据集图数据逻辑回归模型复习逻辑回归和决策边界绘图决策边界恭喜 目标 在本实验中&#xff0c;你将:绘制逻辑回归模型的决策边界。这会让你更好地理解模型的预测。 import numpy as np %matplotlib widget import matplotlib.pyplot as plt from lab_utils_co…

Python逆向:pyc字节码转py文件

一、 工具准备 反编译工具&#xff1a;pycdc.exe 十六进制编辑器&#xff1a;010editor 二、字节码文件转换 在CTF中&#xff0c;有时候会得到一串十六进制文件&#xff0c;通过010editor使用查看后&#xff0c;怀疑可能是python的字节码文件。 三、逆向反编译 将010editor得到…

【网络工程师进阶之路】BFD技术

个人名片&#xff1a;&#x1faaa; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&a…

第十四届蓝桥杯蜗牛

蜗牛 线性dp 目录 蜗牛 线性dp 先求到达竹竿底部的状态转移方程 求蜗牛到达第i根竹竿的传送门入口的最短时间​编辑 题目链接&#xff1a;蓝桥杯2023年第十四届省赛真题-蜗牛 - C语言网 关键在于建立数组将竹竿上的每个状态量表示出来&#xff0c;并分析出状态转移方程 in…