AWS S3 Lambda Python脚本函数实现图片自动转换为webp并上传至s3

Amazon S3 自动转换图片格式

Amazon S3 存储桶 新增文件自动触发 AWS Lambda。Lambda 取 S3 文件做转换并存回去 S3 同一个目录下,并增加相应的后缀名。 并且支持通过API Gateway的方式触发对图片进行修改并输出。 本 Lab 使用 Python Pillow 做图片转换,读者可以参考 Pillow 文档进行功能扩展。

复制代码
import os
import logging
import boto3
import json
import io
import urllib.parse
import PIL
from PIL import Image
from botocore.exceptions import ClientError

os.environ = {
    "preserv-original_format": "False",
    "convert_format": "WebP",
    "convert_postfix": ".webp",
    "resize_feature": "FixSize",
    "resize_Percentile_w": "0.5",
    "resize_Percentile_h": "0.5",
    "resize_Fixsize_w": "640",
    "resize_FixSize_h": "640",
    "save_quality": "95",
    "jpeg_progressive":
        "True",
    "auto_orientation": "True"
}

preserv_original_format = os.environ['preserv_original_format'] == 'True'
# True: detect original image format and keep it to target
# False: convert original image to the format as below
convert_format = os.environ['convert_format']  # Target format
convert_postfix = os.environ['convert_postfix']  # Target file postfix
resize_feature = os.environ['resize_feature']  # Disable: do not resize image

# PercentileNoRatio: resize base on below resize_Percentile and DO NOT keep ratio
# FixSizeNoRatio: resize base on below FixSize and DO NOT keep ratio
resize_Percentile_w = float(os.environ['resize_Percentile_w'])
resize_Percentile_h = float(os.environ['resize_Percentile_h'])
resize_FixSize_w = int(os.environ['resize_FixSize_w'])
resize_Fixsize_h = int(os.environ['resize_FixSize_h'])

save_quality = int(os.environ['save_quality'])  # output image quality for webp, jpeg ...
jpeg_progressive = os.environ['jpeg_progressive'] == 'True'  # progressive mode for JPEG
auto_orientation = os.environ['auto_orientation'] == 'True'  # auto rotate iange based on exif info

# TODO: Watermark with text,
# TODO: Blur, Contract, Bright, Sharp, Rotate

logger = logging.getLogger()
logger.setLevel(logging.INFO)
key_id = 'yourKeyId'
secret = 'yourSecret'
awsRegion = 'ap-south-1'
client = boto3.client('S3', aws_access_key_id=key_id, aws_secret_access_key=secret, region_name=awsRegion)


# get img from s3
def load_s3(bucket, key):
    logger.info('load from s3://{bucket}/{key}')

    try:
        response = client.get_object(
            Bucket=bucket,
            Key=key
        )
        logger.info(json.dumps(response, default=str))
    except Exception as e:
        logging.error(json.dumps(e, default=str))
        os.exit(e)

    return response[' Body'].read()


# get s3 url
def url_s3(bucket, key):
    logger.info('url from s3://{bucket}/{key}')

    try:
        response = client.generate_presigned_url(
            ClientMethod='get_object',
            Params={'Bucket': bucket, 'Key': key},
            ExpiresIn=3600, HttpMethod='Get'
        )

        logger.info(json.dumps(response, default=str))
    except Exception as e:
        logging.error(json.dumps(e, default=str))
        os.exit(0)
    return response


# convert
def img_convert(body, convent_format):
    try:
        logger.info('ready to open Image By PIL ...')
        im = Image.open(io.BytesIO(body))
        logger.info('open Image By PIL success...')

        if preserv_original_format:
            convert_format = im.format

            # resize
        if resize_feature.lower() == 'percentile':
            logger.info('resizing percentile and keep ratio ...')
            w, h = im.size
            w, h = int(w * resize_Percentile_w), int(h * resize_Percentile_h)
            im.thumbnail((w, h))
        if resize_feature.lower() == 'fixsize':
            logger.info('resizing fixsize and keep ratio ...')
            w, h = im.size
            w, h = int(w * resize_Percentile_w), int(h * resize_Percentile_h)
        im.thumbnail((w, h))
        if resize_feature.lower() == 'fixsize':
            logger.info('resizing fixsize and keep ratio ...')
            w, h = resize_FixSize_w, resize_Fixsize_h
            im.thumbnail((w, h))
        if resize_feature.lower() == 'pencentilenometer':
            logger.info('resizing percentile and ignore ratio ...')
            w, h = im.size
            w, h = int(w * resize_Percentile_w), int(h * resize_Percentile_h)
            im = im.resize((w, h), resample=Image.BICUBIC)
        if resize_feature.lower() == 'fixsizeoratio':
            logger.info('resizing fixsizeoratio and igore ratio...')
            w, h = resize_FixSize_w,
            resize_Fixsize_h
            im = im.resize((w, h), resample=Image.BICUBIC)

        logger.info('target size: {im.size}')

        # convert PNG RGBA mode to RGB for JPEG
        if im.mode != 'RGB' and convert_format.lower() == 'jpeg':
            im = im.convert(mode='RGB')

        # save to target format
        in_mem_img = io.BytesIO()
        im.save(in_mem_img, format=convert_format, lossless=True, quality=save_quality, progressive=jpeg_progressive)
        in_mem_img.seek(0)

    except Exception as e:
        logger.error(json.dumps(e, default=str))
        os.exit(0)

    return in_mem_img


# put img to s3
def save_s3(bucket, key, body):
    logger.info(' save to s3://{bucket}/{key}')

    try:
        response = client.put_object(
            Bucket=bucket,
            Key=key, Body=body,
            ContentType="image/webp"
        )
        logger.info(json.dumps(response, default=str))
    except Exception as e:
        logging.error(json.dumps(e, default=str))
        os.exit(0)
    return


# change key from input/ to output/ and change postfix
def change_key(key, convert, postfix):
    key_pre = os.path.splitext(key)[0]

    if not preserv_original_format:
        key_new = key_pre + convert_postfix
    else:
        key_new = key_pre + os.path.splitext(key)[1]
    return key_new

# check image is exists
def exists_image(bucket, key):
    try:
        response = client.head_object(Bucket=bucket, Key=key)
        return True
    except ClientError as ce:
        if ce.response["Error"]["Code"] == "404":
            print("Key: {key} does not exist!")
        else:
            print("Something else went wrong")
        return False
    except Exception as e:
        return False


def lambda_handler(event, context):
    logger.info(json.dumps(event, default=str))
    bucket = 'your_bucket'
    key = 'input/20201106-a1.jpg?image_process=image/format, webp'
    format_key = "image_process=image/format,"

    # 是否包含要做转换的格式
    if format_key not in key:
        return

    source_key = key.split(format_key)[0]
    image_format = key.split(format_key)[1]

    # 转换目标格式
    filepath, tempfilename = os.path.split(source_key)
    filename, extension = os.path.splitext(tempfilename)

    target_key = filepath + '/' + filename + '.' + image_format

    first_req = url_s3(bucket, target_key)

    print(first_req)

    # 是否已存在webp
    if exists_image(bucket, target_key):
        return {
            'statusCode': 301,
            "body": 'Redirect',
            "Location": first_req
        }

    else:
        # 1. 加载原图
        img_org = load_s3(bucket, source_key)
        # 2.转换为目标格式
        body = img_convert(img_org, convert_format)
        # 3.放到新目录
        # key_new = change_key(source_key, convert_postfix)
        # 4.webp上传至s3
        save_s3(bucket, target_key, body)
        # 5.返回webp的地址
        new_img_org = url_s3(bucket, target_key)

        target_key = filepath + '/' + filename + '.' + image_format
        first_req = url_s3(bucket, target_key)
        print(first_req)

        # 是否已存在webp
        if exists_image(bucket, target_key):
            return {
                'statusCode': 301,
                'body': 'Redirect',
                "Location": first_req
            }

        else:
            # 1. 加载原图
            img_org = load_s3(bucket, source_key)
            # 2.转换为目标格式
            body = img_convert(img_org, convert_format)
            # 3.放到新目录
            # key_new = change_key(source_key, convert_postfix)
            # 4.webp上传至s3
            save_s3(bucket, target_key, body)
            # 5.返回webp的地址
            new_img_org = url_s3(bucket, target_key)
            return {
                'statusCode': 301,
                "body": 'Redirect',
                "Location": new_img_org
            }

if __name__ == '__main__':
    print("main function start.")
    result = lambda_handler('', '')
    print("handle success. %s", result)

参考文章

https://github.com/xffss/AmazonS3transferimage/blob/69fe698c66f9fc4fa4b96a00843b1556a395b4b1/S3transferimage.ipynb
本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。

首发链接:https://www.cnblogs.com/lingyejun/p/18169968

相关推荐
晴天165 分钟前
HarmonyOS 和 React 对比-Day22
前端·华为·harmonyos
卷无止境6 分钟前
手写 SQL 在 Tortoise ORM 里到底能派上什么用场
后端·python·fastapi
zone_z7 分钟前
Oracle 数据库基础故障排查系列(12 篇)
数据库·oracle·dba·troubleshooting·blog-series
龙虾PRO9 分钟前
2026 DeepSeek Harness 部署完整教程:npx 一键启动至 Python SDK 全流程接入
开发语言·python
卷无止境10 分钟前
FastAPI、Tortoise ORM 与 PostgreSQL 三件套 是否好用呢?
后端·python·fastapi
DBA_G11 分钟前
GBase 8a数据库RANGE分区操作详细解读
数据库·oracle
都叫我大帅哥17 分钟前
@EnableTransactionManagement 详解:Spring 声明式事务的基石
java·spring
某不知名網友1 小时前
MySQL数据表详解|创建、查看、修改、删除实战教程
数据库·mysql
Wang's Blog1 小时前
PostgreSQL笔记20: 实例、集群、模板数据库与表空间 —— 逻辑体系架构深度解析
数据库·笔记·postgresql
2602_959960921 小时前
大厂Java面试:Spring Boot、JVM、Redis、Kafka与Elasticsearch在内容社区UGC场景下的实战拷问
java·jvm·spring boot·redis·面试题