Featured image of post Python 脚本 - 将图片转换为 WebP 并上传到图床

Python 脚本 - 将图片转换为 WebP 并上传到图床

介绍

这个脚本是一个用于批量处理图像文件的命令行工具,它具备以下功能:

  • 图像转换:使用 cwebp 命令将图像文件转换为 WebP 格式,并保存到输出目录。
  • 图像上传:使用 PicGo 工具上传转换后的图像文件,并从上传结果中提取图像 URL。
  • 生成 Markdown 链接:为上传的图像生成 Markdown 格式的链接。
  • 同时生成适用于网络的压缩图片和高清图片,并在 Markdown 链接中进行嵌套,达成渲染使用网络图片,点击图片打开高清大图的功能。

前置要求

  1. 安装 python3
  2. 安装 cwebp
  3. 安装 picgo-core

使用方法

python3 upload_webp_images.py <input_dir> <output_dir>
  • <input_dir>:需要转换的图片所在文件夹。
  • <output_dir>:转换完成的图片输出文件夹,默认会生成多个子文件夹。

指定要转换的文件类型

python3 upload_webp_images.py <input_dir> <output_dir> --ext <extension>
  • <extension>:默认为 all,大小写不敏感。支持 JPG, PNG, TIFF, GIF, WebP。

是否遍历子文件夹

默认不遍历子文件夹,只在当前文件夹下搜索图片,如需开启遍历子文件夹,可加入 --recursive 参数。

python3 upload_webp_images.py <input_dir> <output_dir> --recursive

脚本代码

import argparse
import subprocess
import re
from pathlib import Path
import logging

# Enable logger, print to console with timestamp and log level
logging.basicConfig(
    level=logging.INFO, format="\n%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)


def expand_image_extensions(extensions: str) -> list[str]:
    """
    Expands a comma-separated list of image extensions to include common variants.

    Args:
    extensions (str): A comma-separated list of image extensions.

    Returns:
    list[str]: A list of expanded image extensions.
    """
    expanded_extensions = []
    for ext in extensions.split(","):
        ext = ext.strip().lower()
        if ext in ["jpg", "jpeg"]:
            expanded_extensions.extend(["jpg", "jpeg"])
        elif ext in ["tif", "tiff"]:
            expanded_extensions.extend(["tif", "tiff"])
        else:
            expanded_extensions.append(ext)

    return expanded_extensions


def find_image_files(
    directory: Path, extensions: list[str], recursive: bool
) -> list[Path]:
    """
    Finds all image files in the given directory with the specified extensions.

    Args:
    directory (Path): The directory to search for image files.
    extensions (list[str]): A list of image file extensions.
    recursive (bool): Whether to search recursively in subdirectories.

    Returns:
    list[Path]: A list of paths to the found image files.
    """
    if recursive:
        return [file for ext in extensions for file in directory.rglob(f"*.{ext}")]
    else:
        return [file for ext in extensions for file in directory.glob(f"*.{ext}")]


def create_output_directories(base_dir: Path, *folder_names: str) -> None:
    """
    Creates the necessary output directories within the base directory.

    Args:
    base_dir (Path): The base directory where the output directories will be created.
    *folder_names (str): Variable number of folder names to create.
    """
    for folder_name in folder_names:
        Path(base_dir, folder_name).mkdir(parents=True, exist_ok=True)


def convert_image_with_cwebp(input_path: Path, output_dir: Path, options: list) -> None:
    """
    Converts an image using cwebp and saves it to the output directory.

    Args:
    input_path (Path): The path to the input image file.
    output_dir (Path): The directory where the converted image will be saved.
    options (list): A list of options to pass to the cwebp command.
    """
    cmd = [
        "cwebp",
        *options,
        "-metadata",
        "all",
        str(input_path),
        "-o",
        str(build_converted_path(input_path, output_dir)),
    ]
    try:
        subprocess.run(cmd, check=True)
    except subprocess.CalledProcessError as e:
        logger.error(f"Failed to convert {input_path}: {e}")


def build_converted_path(input_path: Path, output_dir: Path) -> Path:
    """
    Builds the path for the converted image file.

    Args:
    input_path (Path): The path to the input image file.
    output_dir (Path): The directory where the converted image will be saved.

    Returns:
    Path: The path to the converted image file.
    """
    return output_dir / (input_path.stem + ".webp")


def upload_image(image_path: Path) -> str:
    """
    Uploads an image using PicGo and extracts the uploaded image URL.

    Args:
    image_path (Path): The path to the image file to upload.

    Returns:
    str: The URL of the uploaded image, or an empty string if the upload fails.
    """
    try:
        result = subprocess.run(
            ["picgo", "upload", str(image_path)], capture_output=True, text=True
        )
        match = re.search(r"https://.*?\.webp", result.stdout)
        if match:
            logger.info(f"Uploaded: {match.group(0)}\n")
            return match.group(0)
        else:
            logger.warning("No URL found in upload output.\n")
            return ""
    except subprocess.CalledProcessError as e:
        logger.error(f"Failed to upload {image_path}: {e}\n")
        return ""


def generate_markdown_link(image_name: str, inner_link: str, outer_link: str) -> tuple:
    """
    Generates a markdown link for the image.

    Args:
    image_name (str): The name of the image.
    inner_link (str): The link to the image for the inner markdown link.
    outer_link (str): The link to the image for the outer markdown link.

    Returns:
    tuple: A tuple containing the image name and the markdown link, or an empty tuple if no links are provided.
    """
    return (
        (image_name, f"[![{image_name}]({inner_link})]({outer_link})")
        if inner_link and outer_link
        else ()
    )


def write_markdown_links_to_file(name_link_list: list[tuple], file_path: Path) -> None:
    """
    Writes markdown links to a file.

    Args:
    name_link_list (list[tuple]): A list of tuples containing image names and markdown links.
    file_path (Path): The path to the file where the links will be written.
    """
    failed_uploads = [image_name for image_name, link in name_link_list if not link]

    successful_uploads = [link for _, link in name_link_list if link]

    with open(file_path, "w") as file:
        for link in successful_uploads:
            file.write(link + "\n\n")
        file.write("Successfully uploaded: " + str(len(successful_uploads)) + "\n")

        for image_name in failed_uploads:
            file.write(f"Failed to upload: {image_name}\n\n")
        file.write("Failed to upload: " + str(len(failed_uploads)) + "\n")


def process_single_image(
    image_path: Path, output_dir: Path, md_links_file: Path
) -> tuple:
    """
    Processes a single image file: converts it, uploads it, and writes the markdown link to a file.

    Args:
    image_path (Path): The path to the input image file.
    output_dir (Path): The directory where the converted images will be saved.
    md_links_file (Path): The file where the markdown links will be written.

    Returns:
    tuple: A tuple containing the inner and outer links of the uploaded image.
    """
    large_options = ["-size", "6291456", "-pass", "10", "-qrange", "80", "100", "-mt"]
    website_options = ["-resize", "1280", "0", "-mt"]

    large_dir = output_dir / "6MB"
    website_dir = output_dir / "Website"

    create_output_directories(output_dir, "6MB", "Website")

    convert_image_with_cwebp(image_path, large_dir, large_options)
    outer_link = upload_image(build_converted_path(image_path, large_dir))

    convert_image_with_cwebp(image_path, website_dir, website_options)
    inner_link = upload_image(build_converted_path(image_path, website_dir))

    return (inner_link, outer_link)


def process_images(
    input_dir: Path,
    output_dir: Path,
    extensions: str,
    recursive: bool,
    md_links_file_name: str,
) -> None:
    """
    Processes all image files in the input directory with the specified extensions.

    Args:
    input_dir (Path): The directory containing the input image files.
    output_dir (Path): The directory where the converted images and markdown links will be saved.
    extensions (str): A comma-separated list of image extensions.
    recursive (bool): Whether to search recursively in subdirectories.
    md_links_file_name (str): The name of the file where the markdown links will be written.
    """
    image_files = find_image_files(input_dir, extensions, recursive)

    md_links_path = output_dir / "Website" / md_links_file_name

    name_link_list = []

    for image_file in image_files:
        (inner_link, outer_link) = process_single_image(
            image_file, output_dir, md_links_path
        )
        name_link_list.append(
            generate_markdown_link(image_file.stem, inner_link, outer_link)
        )

    write_markdown_links_to_file(name_link_list, md_links_path)


def main() -> None:
    """
    The main function that parses command line arguments and processes images.
    """
    parser = argparse.ArgumentParser(description="Convert and upload images.")
    parser.add_argument("input_dir", type=str, help="Input directory path")
    parser.add_argument("output_dir", type=str, help="Output directory path")
    parser.add_argument(
        "--ext",
        type=str,
        default="all",
        help="Comma-separated list of input image extensions",
    )
    parser.add_argument(
        "--recursive",
        action="store_false",
        help="Search for images recursively in subdirectories",
    )
    parser.add_argument(
        "--md_links_file",
        type=str,
        default="markdown_image_links.txt",
        help="Markdown links file name",
    )

    args = parser.parse_args()

    input_dir = Path(args.input_dir)
    output_dir = Path(args.output_dir)

    if args.ext.lower() == "all":
        extensions = ["jpg", "jpeg", "png", "tif", "tiff", "webp"]
    else:
        extensions = expand_image_extensions(args.ext)

    process_images(
        input_dir, output_dir, extensions, args.recursive, args.md_links_file
    )


if __name__ == "__main__":
    main()
使用 Hugo 构建
主题 StackJimmy 设计