208 lines
6.3 KiB
Python
208 lines
6.3 KiB
Python
"""
|
|
Scientific Gallery Generator
|
|
|
|
This module generates static HTML galleries from scientific plot collections.
|
|
It converts PDF plots to PNG thumbnails, creates responsive web interfaces,
|
|
and organizes plots into hierarchical directory structures.
|
|
|
|
Features:
|
|
- PDF to PNG conversion with configurable DPI
|
|
- Incremental updates (only converts when source is newer)
|
|
- Jinja2 templating for consistent HTML generation
|
|
- Support for nested folder structures
|
|
- Responsive grid layout with search and navigation
|
|
"""
|
|
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional
|
|
from datetime import datetime
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
from utils.config import Config
|
|
from utils.metadata import (
|
|
load_folder_metadata,
|
|
merge_metadata,
|
|
save_metadata_cache,
|
|
)
|
|
from utils.processing import (
|
|
process_plot_files,
|
|
needs_update,
|
|
render_gallery_page,
|
|
)
|
|
|
|
|
|
CONFIG = Config.from_yaml("config.yaml")
|
|
|
|
|
|
def datetime_from_timestamp(timestamp: float) -> datetime:
|
|
"""Convert a Unix timestamp to a datetime object."""
|
|
return datetime.fromtimestamp(timestamp)
|
|
|
|
|
|
def strftime_filter(dt: datetime, fmt: str) -> str:
|
|
"""Format a datetime object using strftime."""
|
|
return dt.strftime(fmt)
|
|
|
|
|
|
env = Environment(loader=FileSystemLoader("."))
|
|
env.filters['datetime_from_timestamp'] = datetime_from_timestamp
|
|
env.filters['strftime'] = strftime_filter
|
|
template = env.get_template("templates/gallery.html")
|
|
|
|
|
|
def build_gallery(
|
|
source_dir: Path,
|
|
web_dir: Path,
|
|
relative_path: Path = None,
|
|
inherited_metadata: Optional[Dict[str, Any]] = None,
|
|
) -> None:
|
|
"""
|
|
Recursively build gallery structure from source directory.
|
|
|
|
Processes all PDF files in the source directory, converts them to PNG,
|
|
copies both to the web directory, and generates index.html files with
|
|
navigation and thumbnails. Now includes metadata support.
|
|
|
|
Args:
|
|
source_dir: Source directory containing PDF files
|
|
web_dir: Target web directory for gallery output
|
|
relative_path: Relative path from gallery root (for navigation)
|
|
inherited_metadata: Metadata inherited from parent directories
|
|
"""
|
|
if relative_path is None:
|
|
relative_path = Path(".")
|
|
|
|
if inherited_metadata is None:
|
|
inherited_metadata = {}
|
|
|
|
folder_metadata = load_folder_metadata(source_dir)
|
|
current_metadata = merge_metadata(inherited_metadata, folder_metadata)
|
|
|
|
# Find all plot files (both PDF and HTML)
|
|
pdf_files = list(source_dir.glob("*.pdf"))
|
|
html_files = list(source_dir.glob("*.html"))
|
|
plot_files = pdf_files + html_files
|
|
|
|
items = []
|
|
plot_metadata_cache = {}
|
|
|
|
# Process all plot files (PDFs and HTMLs)
|
|
for plot_file in plot_files:
|
|
item = process_plot_files(
|
|
CONFIG=CONFIG,
|
|
plot_file=plot_file,
|
|
web_dir=web_dir,
|
|
current_metadata=current_metadata,
|
|
)
|
|
items.append(item)
|
|
plot_metadata_cache[plot_file.stem] = item["metadata"]
|
|
|
|
save_metadata_cache(web_dir, plot_metadata_cache)
|
|
|
|
subdirs = [d for d in source_dir.iterdir() if d.is_dir()]
|
|
subdir_names = []
|
|
for subdir in subdirs:
|
|
subdir_web = web_dir / subdir.name
|
|
subdir_web.mkdir(exist_ok=True)
|
|
subdir_relative = relative_path / subdir.name
|
|
build_gallery(subdir, subdir_web, subdir_relative, current_metadata)
|
|
subdir_names.append(subdir.name)
|
|
|
|
render_gallery_page(
|
|
CONFIG=CONFIG,
|
|
template=template,
|
|
web_dir=web_dir,
|
|
items=items,
|
|
subdirs=subdir_names,
|
|
relative_path=relative_path,
|
|
metadata=current_metadata
|
|
)
|
|
|
|
|
|
def main(clean_first: bool = False) -> None:
|
|
"""
|
|
Main entry point for gallery generation.
|
|
|
|
Args:
|
|
clean_first: If True, removes and recreates the gallery directory
|
|
|
|
Processes all configured sources and generates the complete gallery
|
|
structure in the web directory. Ensures assets are available.
|
|
"""
|
|
gallery_root = Path(CONFIG.web_folder) / CONFIG.plot_root
|
|
|
|
if clean_first and gallery_root.exists():
|
|
print(f"Cleaning gallery directory {gallery_root}...")
|
|
shutil.rmtree(gallery_root)
|
|
|
|
gallery_root.mkdir(parents=True, exist_ok=True)
|
|
assets_src = Path("assets")
|
|
assets_dst = gallery_root.parent / "assets"
|
|
|
|
if assets_src.exists():
|
|
main_css_src = assets_src / "css" / "main.css"
|
|
main_css_dst = assets_dst / "css" / "main.css"
|
|
if not assets_dst.exists() or needs_update(main_css_src, main_css_dst):
|
|
if assets_dst.exists():
|
|
shutil.rmtree(assets_dst)
|
|
shutil.copytree(assets_src, assets_dst)
|
|
print(f"Updated assets from {assets_src} to {assets_dst}")
|
|
else:
|
|
print(f"Warning: Assets directory {assets_src} not found")
|
|
|
|
source_subdirs = []
|
|
|
|
for source in CONFIG.sources:
|
|
source_path = Path(source.path)
|
|
source_web_dir = gallery_root / source.name
|
|
source_web_dir.mkdir(parents=True, exist_ok=True)
|
|
source_subdirs.append(source.name)
|
|
|
|
if source_path.is_file() and source_path.suffix == '.pdf':
|
|
item = process_plot_files(
|
|
CONFIG=CONFIG,
|
|
plot_file=source_path,
|
|
web_dir=source_web_dir,
|
|
)
|
|
render_gallery_page(
|
|
CONFIG=CONFIG,
|
|
template=template,
|
|
web_dir=source_web_dir,
|
|
items=[item],
|
|
subdirs=[],
|
|
relative_path=Path(source.name)
|
|
)
|
|
elif source_path.is_dir():
|
|
build_gallery(source_path, source_web_dir, Path(source.name))
|
|
else:
|
|
print(
|
|
f"Warning: Source {source.path} is neither a "
|
|
f"directory nor a PDF file. Skipping."
|
|
)
|
|
|
|
print(f"Processed {source.name}: {source.path}")
|
|
|
|
render_gallery_page(
|
|
CONFIG=CONFIG,
|
|
template=template,
|
|
web_dir=gallery_root,
|
|
items=[],
|
|
subdirs=source_subdirs,
|
|
relative_path=Path("."),
|
|
title="Gallery Root"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Generate gallery')
|
|
parser.add_argument(
|
|
'--clean',
|
|
action='store_true',
|
|
help='Clean gallery directory before generation'
|
|
)
|
|
args = parser.parse_args()
|
|
main(clean_first=args.clean)
|