概述
这个 Python 脚本旨在自动化上传图片到服务器并为这些图片生成 Markdown 链接的过程。它支持多种图片格式,并能递归搜索目录中的图片。脚本还具备处理图片对的能力,并生成包含上传图片链接的 Markdown 文件。
特性
- 支持多种图片格式(jpg, jpeg, png, tif, tiff, webp)。
- 递归搜索目录中的图片。
- 使用子进程调用命令行工具(假设为
upic
)上传图片。 - 生成上传图片的 Markdown 链接。
- 处理图片对,以便进行并排比较。
- 提供详细的成功上传和失败日志。
- 文件去重。
安装
要使用此脚本,您需要在系统上安装 Python。此外,您还需要安装 upic
命令行工具以配置图片上传。
-
从 python.org 安装 Python。
-
安装
fdupes
用于文件去重.brew install fdupes
-
安装 uPic, 或者
-
安装 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()