""" 独立图片预览 HTTP 服务 启动方式: python image_server.py [端口] 默认端口: 8080 """ import os import sys import json import mimetypes import http.server from pathlib import Path from urllib.parse import unquote, urlparse, parse_qs # 图片根目录(与 settings/base.py 中 FILESPACE_ROOT_PATH 默认值一致) IMAGE_ROOT = Path(r"D:\00_Workspace\python\model_train\project_space") IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".tif"} BASE_DIR = Path(__file__).resolve().parent TEMPLATE = r""" 图片预览 - {title}

图片预览服务

{breadcrumb}
{body}
""" def is_image(filename: str) -> bool: return Path(filename).suffix.lower() in IMAGE_EXTENSIONS def get_breadcrumb_html(relative_path: str) -> str: """生成面包屑导航""" if not relative_path or relative_path == ".": return '根目录' parts = relative_path.replace("\\", "/").strip("/").split("/") links = ['根目录'] accumulated = "" for p in parts: accumulated += "/" + p links.append(f'{p}') return " / ".join(links) def get_parent_dir(relative_path: str) -> str: if not relative_path or relative_path == ".": return "" parent = str(Path(relative_path).parent) return "" if parent == "." else parent def build_page_html(current_dir: str, view_mode: str, port: int) -> str: root = IMAGE_ROOT if current_dir: scan_path = root / current_dir else: scan_path = root dir_param = f"dir={current_dir}&" if current_dir else "" if not scan_path.exists(): body = f'
目录不存在: {scan_path}
' return TEMPLATE.format( title="目录不存在", breadcrumb=get_breadcrumb_html(current_dir), dir_param=dir_param, body=body, current_view=view_mode, gallery_active="active" if view_mode != "list" else "", list_active="active" if view_mode == "list" else "", image_list="[]", port=port, ) folders = [] images = [] try: for entry in sorted(scan_path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())): if entry.name.startswith("."): continue if entry.is_dir(): folders.append(entry.name) elif is_image(entry.name): images.append(entry.name) except PermissionError: pass body_parts = [] # 返回上级目录 if current_dir and current_dir != ".": parent = get_parent_dir(current_dir) body_parts.append( f'
' f'📁../
' ) # 子目录 if folders: parts = [] for f in folders: sub_dir = f"{current_dir}/{f}".lstrip("/") if current_dir else f parts.append( f'' f'📁{f}' ) body_parts.append(f'
{"".join(parts)}
') # 图片 if images: image_list_data = [] for img in images: if current_dir: rel = f"{current_dir}/{img}".replace("\\", "/") else: rel = img image_list_data.append({"name": img, "path": rel}) if view_mode == "list": rows = [] for i, img_info in enumerate(image_list_data): rows.append( f'{i+1}' f'{img_info["name"]}' f'预览' ) body_parts.append( '' '' '' '' '' + "".join(rows) + "
#文件名操作
" ) else: cards = [] for i, img_info in enumerate(image_list_data): cards.append( f'
' f'' f'
{img_info["name"]}
' f'
' ) body_parts.append(f'') body = "".join(body_parts) return TEMPLATE.format( title=current_dir or "根目录", breadcrumb=get_breadcrumb_html(current_dir), dir_param=dir_param, body=body, current_view=view_mode, gallery_active="active" if view_mode != "list" else "", list_active="active" if view_mode == "list" else "", image_list=json.dumps(image_list_data), port=port, ) else: if folders: body = "".join(body_parts) else: body_parts.append('
此目录下没有图片
') body = "".join(body_parts) return TEMPLATE.format( title=current_dir or "根目录", breadcrumb=get_breadcrumb_html(current_dir), dir_param=dir_param, body=body, current_view=view_mode, gallery_active="active" if view_mode != "list" else "", list_active="active" if view_mode == "list" else "", image_list="[]", port=port, ) class ImagePreviewHandler(http.server.SimpleHTTPRequestHandler): """自定义请求处理器""" def __init__(self, *args, **kwargs): super().__init__(*args, directory=str(BASE_DIR), **kwargs) def do_GET(self): parsed = urlparse(self.path) path = unquote(parsed.path) params = parse_qs(parsed.query) # 根路径 → HTML 页面 if path == "/": current_dir = params.get("dir", [""])[0] view_mode = params.get("view", ["gallery"])[0] html = build_page_html(current_dir, view_mode, PORT) self._respond_html(html) return # /file/ 路径 → 返回实际图片文件 if path.startswith("/file/"): file_rel = path[len("/file/"):] file_path = IMAGE_ROOT / file_rel if file_path.exists() and file_path.is_file(): self._serve_file(file_path) else: self.send_error(404, f"File not found: {file_rel}") return # 其他路径 → 默认静态文件处理 super().do_GET() def _respond_html(self, html: str): data = html.encode("utf-8") self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(data))) self.send_header("Cache-Control", "no-cache") self.end_headers() self.wfile.write(data) def _serve_file(self, file_path: Path): mime_type, _ = mimetypes.guess_type(str(file_path)) if mime_type is None: mime_type = "application/octet-stream" file_size = file_path.stat().st_size self.send_response(200) self.send_header("Content-Type", mime_type) self.send_header("Content-Length", str(file_size)) self.send_header("Cache-Control", "public, max-age=3600") self.end_headers() with open(file_path, "rb") as f: self.wfile.write(f.read()) def log_message(self, format, *args): print(f"[{self.log_date_time_string()}] {args[0]}") def main(): global PORT PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080 # 确保图片根目录存在 if not IMAGE_ROOT.exists(): os.makedirs(IMAGE_ROOT, exist_ok=True) print(f"已创建目录: {IMAGE_ROOT}") server = http.server.HTTPServer(("0.0.0.0", PORT), ImagePreviewHandler) print(f"=" * 55) print(f" 图片预览服务已启动") print(f" 图片根目录: {IMAGE_ROOT}") print(f" 访问地址: http://localhost:{PORT}") print(f" 按 Ctrl+C 停止服务") print(f"=" * 55) try: server.serve_forever() except KeyboardInterrupt: print("\n服务已停止") server.server_close() if __name__ == "__main__": main()