介绍
这个脚本是一个用于批量处理图像文件的命令行工具,它具备以下功能:
- 图像转换:使用 cwebp 命令将图像文件转换为 WebP 格式,并保存到输出目录。
- 图像上传:使用 PicGo 工具上传转换后的图像文件,并从上传结果中提取图像 URL。
- 生成 Markdown 链接:为上传的图像生成 Markdown 格式的链接。
- 同时生成适用于网络的压缩图片和高清图片,并在 Markdown 链接中进行嵌套,达成渲染使用网络图片,点击图片打开高清大图的功能。
前置要求
- 安装
python3
- 安装
cwebp
- 安装
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()