Convert to proper python package

This commit is contained in:
Kylian Schmidt
2026-04-22 13:48:23 +02:00
parent 7e36688d1d
commit 27ea17246c
52 changed files with 6888 additions and 246 deletions
+52 -199
View File
@@ -1,130 +1,26 @@
"""
Scientific Gallery Generator
Scientific Gallery Generator - Legacy CLI Entry Point
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.
This module provides backward compatibility for the legacy CLI interface.
For new development, use the gallery package API directly:
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
from gallery import generate, GalleryConfig
config = GalleryConfig.from_yaml("config.yaml")
generate(config, verbose=True)
Or use the new CLI:
gallery --config config.yaml --verbose
"""
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, GalleryItem
from utils.metadata import (
load_folder_metadata,
merge_metadata,
save_metadata_cache,
)
from utils.processing import (
process_plot_files,
needs_update,
render_gallery_page,
)
from gallery import generate
from gallery.config import GalleryConfig
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:
def main(clean_first: bool = False, source_override: str = 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, source_override: Optional[str] = None
) -> None:
"""
Main entry point for gallery generation.
Main entry point for gallery generation (legacy interface).
Args:
clean_first: If True, removes and recreates the gallery directory
@@ -134,93 +30,50 @@ def main(
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
try:
# Load configuration from config.yaml
config = GalleryConfig.from_yaml("config.yaml")
if clean_first and gallery_root.exists():
print(f"Cleaning gallery directory {gallery_root}...")
shutil.rmtree(gallery_root)
# Handle source override
if source_override:
from pathlib import Path
from gallery.config import GallerySource
gallery_root.mkdir(parents=True, exist_ok=True)
assets_src = Path("assets")
assets_dst = gallery_root.parent / "assets"
source_path = Path(source_override).resolve()
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")
# Check if source is in config
matching_source = None
for source in config.sources:
if Path(source.path).resolve() == source_path:
matching_source = source
break
# Determine which sources to process
if source_override:
source_path = Path(source_override).resolve()
# If not in config, create a temporary source entry
if matching_source is None:
source_name = source_path.name
print(
f"Source {source_override} not in config. "
f"Adding temporarily as '{source_name}'"
)
config.sources = [
GallerySource(name=source_name, path=source_path)
]
else:
config.sources = [matching_source]
# Check if source is in config
matching_source = None
for source in CONFIG.sources:
if Path(source.path).resolve() == source_path:
matching_source = source
break
# Generate gallery using the new API
success = generate(
config=config,
clean_first=clean_first,
verbose=True
)
# If not in config, create a temporary source entry
if matching_source is None:
source_name = source_path.name
msg = (
f"Source {source_override} not in config. "
f"Adding temporarily as '{source_name}'"
)
print(msg)
matching_source = GalleryItem(name=source_name, path=source_path)
if not success:
exit(1)
sources_to_process = [matching_source]
else:
sources_to_process = CONFIG.sources
source_subdirs = []
for source in sources_to_process:
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"
)
except Exception as e:
print(f"Error: {e}")
exit(1)
if __name__ == "__main__":