Featured image of post Python 脚本 - 图片上传与 Markdown 链接生成器

Python 脚本 - 图片上传与 Markdown 链接生成器

概述

这个 Python 脚本旨在自动化上传图片到服务器并为这些图片生成 Markdown 链接的过程。它支持多种图片格式,并能递归搜索目录中的图片。脚本还具备处理图片对的能力,并生成包含上传图片链接的 Markdown 文件。

特性

  • 支持多种图片格式(jpg, jpeg, png, tif, tiff, webp)。
  • 递归搜索目录中的图片。
  • 使用子进程调用命令行工具(假设为 upic)上传图片。
  • 生成上传图片的 Markdown 链接。
  • 处理图片对,以便进行并排比较。
  • 提供详细的成功上传和失败日志。
  • 文件去重。

安装

要使用此脚本,您需要在系统上安装 Python。此外,您还需要安装 upic 命令行工具以配置图片上传。

  1. python.org 安装 Python。

  2. 安装 fdupes 用于文件去重.

    brew install fdupes
    
  3. 安装 uPic, 或者

  4. 安装 PicGo-Core

使用方法

要使用脚本,请从命令行运行并带上所需的参数。以下是可用参数的详细说明:

  • image_dir_list:要搜索图片的目录列表。至少需要一个目录。
  • -i, --inner_image_dir:内层图片的目录,通常是较大的图片。如果指定,image_dir_list 只能包含一个目录。
  • --ext:输入图片扩展名的逗号分隔列表。默认为 ["all"]
  • --md_links:输出 Markdown 链接的文件名。默认为 "markdown_links.txt"
  • -s, --subdir:一个标志,表示是否递归搜索子目录。默认为 True

运行脚本的示例命令:

python script_name.py /path/to/images -i /path/to/inner/images --ext jpg png --md_links links.md

输出

脚本将生成一个包含上传图片链接的 Markdown 文件。它还会记录成功上传和任何失败的信息。

日志记录

脚本使用 Python 的 logging 模块将信息记录到控制台。日志级别设置为 INFO,日志包括时间戳和日志级别。

代码

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

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


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

    Args:
    extensions (list[str]): A  list of image extensions.

    Returns:
    list[str]: A list of expanded image extensions.
    """
    supported_extensions = ["jpg", "jpeg", "png", "tif", "tiff", "webp"]

    expanded_extensions = []
    for ext in [s.lower() for s in extensions]:
        if ext == "all":
            expanded_extensions = supported_extensions
            break
        elif 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 [s.lower() for s in expanded_extensions] + [
        s.upper() for s in expanded_extensions
    ]


def find_image_files(
    directory: Path, extensions: list[str], subdir: 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.
    subdir (bool): Whether to search recursively in subdirectories.

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


def upload_single_image(image_path: Path) -> str:
    """
    Upload an image using PicGo and extract 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)],
            ["upic", "-u", str(image_path.absolute()), "-o", "url", "-s"],
            capture_output=True,
            text=True,
            check=True,
        )
        match = re.search(rf"https://.*?{image_path.suffix}", 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 upload_image_pair(
    inner_image_path: Path, outer_image_path: Path
) -> tuple[str, str]:
    """
    Uploads two images to a server and returns their URLs.

    Args:
        inner_image_path (Path): The file path to the inner image.
        outer_image_path (Path): The file path to the outer image.

    Returns:
        Tuple[str, str]: A tuple containing the URLs of the uploaded images.
                          If either image fails to upload, an empty tuple is returned.
    """
    outer_url = upload_single_image(outer_image_path)
    if outer_url:
        inner_url = upload_single_image(inner_image_path)
    if inner_url and outer_url:
        return (inner_url, outer_url)
    return ()


def process_links(
    image_dir_list, inner_image_dir, extensions, markdown_links_filename, subdir
):
    """
    Generate markdown links for webp images in the given directories.

    Args:
    - image_dir_list (list of str): List of directories containing webp images.
    - inner_image_dir (str): Directory for inner images.
    - extensions (list[str]): A list of image extensions.
    - markdown_links_filename (str): File name for the output markdown links.
    - subdir (bool): Whether to recursively search subdirectories.
    """
    if inner_image_dir:
        outer_image_dir_path = Path(image_dir_list[0])
        inner_image_dir_path = Path(inner_image_dir)
        markdown_links_filepath = inner_image_dir_path / markdown_links_filename

        if not inner_image_dir_path.exists():
            logging.error(f"Directory {inner_image_dir_path} does not exist.")
            return

        inner_image_path_list, outer_image_path_list = find_image_files(
            inner_image_dir_path, extensions, subdir
        ), find_image_files(outer_image_dir_path, extensions, subdir)

        image_stem_and_url_list, failed_upload_image_stem_list = [], []
        for inner_image_path, outer_image_path in zip(
            inner_image_path_list, outer_image_path_list
        ):
            (inner_url, outer_url) = upload_image_pair(
                inner_image_path, outer_image_path
            )
            if inner_url and outer_url:
                image_stem_and_url_list.append(
                    (inner_image_path.stem, inner_url, outer_url)
                )
            else:
                failed_upload_image_stem_list.append(inner_image_path.stem)

        with markdown_links_filepath.open("w") as f:
            f.write(f"Successfully uploaded: {len(image_stem_and_url_list)}\n\n")
            for image_stem, inner_url, outer_url in image_stem_and_url_list:
                f.write(f"[![{image_stem}]({inner_url})]({outer_url})\n\n")

            f.write(f"Failed to upload: {len(failed_upload_image_stem_list)}\n\n")
            for image_stem in failed_upload_image_stem_list:
                f.write(f"{image_stem}\n\n")
    else:
        subprocess.run(["fdupes", "-rdN", str(image_dir_path)], check=True)
        for image_dir_path in map(Path, image_dir_list):

            if not image_dir_path.exists():
                logging.error(f"Directory {image_dir_path} does not exist.")
                continue

            markdown_links_filepath = image_dir_path / markdown_links_filename
            with markdown_links_filepath.open("w") as f:
                failed_upload_image_stem_list = []
                for image_path in find_image_files(image_dir_path, extensions, subdir):
                    url = upload_single_image(image_path)
                    if url:
                        f.write(f"![{image_path.stem}]({url})\n\n")
                    else:
                        failed_upload_image_stem_list.append(image_path.stem)

                f.write(f"Failed to upload: {len(failed_upload_image_stem_list)}\n\n")
                for image_stem in failed_upload_image_stem_list:
                    f.write(f"{image_stem}\n\n")


def main():
    """
    Main function to parse arguments and call the markdown link generator.
    """
    parser = argparse.ArgumentParser(
        description="Generate markdown links for webp images."
    )
    parser.add_argument(
        "image_dir_list", nargs="+", help="List of directories containing webp images."
    )
    parser.add_argument(
        "-i",
        "--inner_image_dir",
        help="Directory for inner images, usually larger images.",
    )
    parser.add_argument(
        "--ext",
        nargs="+",
        default=["all"],
        help="Comma-separated list of input image extensions",
    )
    parser.add_argument(
        "--md_links",
        default="markdown_links.txt",
        help="File name for the output markdown links.",
    )
    parser.add_argument(
        "-s",
        "--subdir",
        action="store_false",
        help="Whether to recursively search subdirectories.",
    )

    args = parser.parse_args()

    if args.inner_image_dir and len(args.image_dir_list) != 1:
        logging.error(
            "If --inner_image_dir is specified, img_dir_list must contain only one element."
        )
        return

    process_links(
        args.image_dir_list,
        args.inner_image_dir,
        expand_image_extensions(args.ext),
        args.md_links,
        args.subdir,
    )


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