#!/usr/bin/env python3 # flake8: noqa: E501 # Copyright (c) 2025 ByteDance Ltd. and/or its affiliates # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Depth Anything 3 Gallery Server (two-level, single-file) Now supports paginated depth preview (4 per page). """ import argparse import json import mimetypes import os import posixpath import sys from functools import partial from http import HTTPStatus from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import quote, unquote # ------------------------------ Embedded HTML ------------------------------ # HTML_PAGE = r""" Depth Anything 3 Gallery

Depth Anything 3 Gallery

Level 1 shows groups only; click a group to browse scenes and previews.

🎯 Depth Anything 3 Gallery

Explore 3D reconstructions and depth visualizations from Depth Anything 3. Browse through groups of scenes, preview 3D models, and examine depth maps interactively.

""" # ------------------------------ Utilities ------------------------------ # IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".webp", ".bmp") def _url_join(*parts: str) -> str: norm = posixpath.join(*[p.replace("\\", "/") for p in parts]) segs = [s for s in norm.split("/") if s not in ("", ".")] return "/".join(quote(s) for s in segs) def _is_plain_name(name: str) -> bool: return all(c not in name for c in ("/", "\\")) and name not in (".", "..") def build_group_list(root_dir: str) -> dict: groups = [] try: for gname in sorted(os.listdir(root_dir)): gpath = os.path.join(root_dir, gname) if not os.path.isdir(gpath): continue has_scene = False try: for sname in os.listdir(gpath): spath = os.path.join(gpath, sname) if not os.path.isdir(spath): continue if os.path.exists(os.path.join(spath, "scene.glb")) and os.path.exists( os.path.join(spath, "scene.jpg") ): has_scene = True break except Exception: pass if has_scene: groups.append({"id": gname, "title": gname}) except Exception as e: print(f"[warn] build_group_list failed: {e}", file=sys.stderr) return {"groups": groups} def build_group_manifest(root_dir: str, group: str) -> dict: items = [] gpath = os.path.join(root_dir, group) try: if not os.path.isdir(gpath): return {"group": group, "items": []} for sname in sorted(os.listdir(gpath)): spath = os.path.join(gpath, sname) if not os.path.isdir(spath): continue glb_fs = os.path.join(spath, "scene.glb") jpg_fs = os.path.join(spath, "scene.jpg") if not (os.path.exists(glb_fs) and os.path.exists(jpg_fs)): continue depth_images = [] dpath = os.path.join(spath, "depth_vis") if os.path.isdir(dpath): files = [ f for f in os.listdir(dpath) if os.path.splitext(f)[1].lower() in IMAGE_EXTS ] for fn in sorted(files): depth_images.append("/" + _url_join(group, sname, "depth_vis", fn)) items.append( { "id": sname, "title": sname, "model": "/" + _url_join(group, sname, "scene.glb"), "thumbnail": "/" + _url_join(group, sname, "scene.jpg"), "depth_images": depth_images, } ) except Exception as e: print(f"[warn] build_group_manifest failed for {group}: {e}", file=sys.stderr) return {"group": group, "items": items} class GalleryHandler(SimpleHTTPRequestHandler): def __init__(self, *args, directory=None, **kwargs): super().__init__(*args, directory=directory, **kwargs) def do_GET(self): if self.path in ("/", "/index.html") or self.path.startswith("/?"): content = HTML_PAGE.encode("utf-8") self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(content))) self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(content) return if self.path == "/manifest.json": data = json.dumps( build_group_list(self.directory), ensure_ascii=False, indent=2 ).encode("utf-8") self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(data))) self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(data) return if self.path.startswith("/manifest/") and self.path.endswith(".json"): group_enc = self.path[len("/manifest/") : -len(".json")] try: group = unquote(group_enc) except Exception: group = group_enc if not _is_plain_name(group): self.send_error(HTTPStatus.BAD_REQUEST, "Invalid group name") return data = json.dumps( build_group_manifest(self.directory, group), ensure_ascii=False, indent=2 ).encode("utf-8") self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(data))) self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(data) return if self.path == "/favicon.ico": self.send_response(HTTPStatus.NO_CONTENT) self.end_headers() return return super().do_GET() def list_directory(self, path): self.send_error(HTTPStatus.NOT_FOUND, "Directory listing disabled") return None def gallery(): parser = argparse.ArgumentParser( description="Depth Anything 3 Gallery Server (two-level, with pagination)" ) parser.add_argument( "-d", "--dir", required=True, help="Gallery root directory (two-level: group/scene)" ) parser.add_argument("-p", "--port", type=int, default=8000, help="Port (default 8000)") parser.add_argument("--host", default="127.0.0.1", help="Host address (default 127.0.0.1)") parser.add_argument("--open", action="store_true", help="Open browser after launch") args = parser.parse_args() root_dir = os.path.abspath(args.dir) if not os.path.isdir(root_dir): print(f"[error] Directory not found: {root_dir}", file=sys.stderr) sys.exit(1) Handler = partial(GalleryHandler, directory=root_dir) server = ThreadingHTTPServer((args.host, args.port), Handler) addr = f"http://{args.host}:{args.port}/" print(f"[info] Serving gallery from: {root_dir}") print(f"[info] Open: {addr}") if args.open: try: import webbrowser webbrowser.open(addr) except Exception as e: print(f"[warn] Failed to open browser: {e}", file=sys.stderr) try: server.serve_forever() except KeyboardInterrupt: print("\n[info] Shutting down...") finally: server.server_close() def main(): """Main entry point for gallery server.""" mimetypes.add_type("model/gltf-binary", ".glb") gallery() if __name__ == "__main__": main()