Skip to content

Commit b69f53a

Browse files
jeremymanningclaude
andcommitted
Add post-build script to fix animated GIF thumbnails
- Created docs/post_build.py to copy GIF thumbnails and update HTML references - Added comprehensive documentation in docs/ANIMATED_THUMBNAILS.md - Script works around sphinx-gallery limitation with GIF thumbnail paths - Enables animated thumbnails to display properly on Read the Docs - Includes instructions for local development and Read the Docs integration Root cause: sphinx-gallery ignores sphinx_gallery_thumbnail_path for GIFs Solution: Post-build processing to copy GIFs and update HTML references 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 297a82a commit b69f53a

File tree

2 files changed

+199
-0
lines changed

2 files changed

+199
-0
lines changed

docs/ANIMATED_THUMBNAILS.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Animated Thumbnails Fix
2+
3+
## Problem
4+
The gallery thumbnails for animated examples (chemtrails, animate_MDS, animate_spin, animate, precog, save_movie) were not displaying as animated GIFs. Instead, they showed static PNG thumbnails or placeholder images.
5+
6+
## Root Cause
7+
Sphinx-gallery ignores the `sphinx_gallery_thumbnail_path` directive when it points to GIF files and generates PNG thumbnails instead. The GIF files are properly stored in version control but need to be manually copied and HTML references updated after the build.
8+
9+
## Solution
10+
A post-build script (`docs/post_build.py`) has been created that:
11+
12+
1. **Copies GIF thumbnails**: Moves animated GIF files from `docs/_static/thumbnails/` to `docs/_build/html/_images/`
13+
2. **Updates HTML references**: Replaces PNG thumbnail references with GIF references in the gallery HTML
14+
15+
## Usage
16+
17+
### Local Development
18+
After building documentation locally:
19+
```bash
20+
cd docs/
21+
make html
22+
python post_build.py
23+
```
24+
25+
### Read the Docs Integration
26+
For Read the Docs, add this to your `.readthedocs.yaml`:
27+
```yaml
28+
build:
29+
os: ubuntu-22.04
30+
tools:
31+
python: "3.11"
32+
jobs:
33+
post_create_environment:
34+
# Install dependencies
35+
- pip install -r requirements.txt
36+
post_build:
37+
# Fix animated thumbnails after build
38+
- python docs/post_build.py
39+
```
40+
41+
### Manual Build Commands
42+
```bash
43+
# Clean build
44+
make clean
45+
make html
46+
python post_build.py
47+
48+
# Or direct sphinx
49+
python -m sphinx.cmd.build -b html . _build/html
50+
python post_build.py
51+
```
52+
53+
## Files Involved
54+
55+
### Animated Examples with Custom Thumbnails
56+
- `examples/chemtrails.py``sphx_glr_chemtrails_thumb.gif`
57+
- `examples/animate_MDS.py``sphx_glr_animate_MDS_thumb.gif`
58+
- `examples/animate_spin.py``sphx_glr_animate_spin_thumb.gif`
59+
- `examples/animate.py``sphx_glr_animate_thumb.gif`
60+
- `examples/precog.py``sphx_glr_precog_thumb.gif`
61+
- `examples/save_movie.py``sphx_glr_save_movie_thumb.gif`
62+
63+
### Static Examples with Custom Thumbnails
64+
- `examples/explore.py``sphx_glr_explore_thumb.png`
65+
- `examples/save_image.py``sphx_glr_save_image_thumb.png`
66+
- `examples/analyze.py``sphx_glr_analyze_thumb.png`
67+
68+
### Key Directories
69+
- `docs/_static/thumbnails/` - Version controlled custom thumbnails (source)
70+
- `docs/_build/html/_images/` - Built documentation images (target)
71+
- `docs/post_build.py` - Automation script
72+
73+
## Technical Details
74+
75+
Each example file includes a `sphinx_gallery_thumbnail_path` comment:
76+
```python
77+
# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_example_thumb.gif'
78+
```
79+
80+
However, sphinx-gallery generates PNG thumbnails regardless of this directive when pointing to GIF files. The post-build script works around this limitation by:
81+
82+
1. Copying the actual GIF files to the correct location
83+
2. Updating the HTML to reference the GIF files instead of PNG files
84+
85+
## Verification
86+
87+
After running the post-build script:
88+
1. Check that GIF files exist in `docs/_build/html/_images/`
89+
2. Open `docs/_build/html/auto_examples/index.html` in a browser
90+
3. Verify that animated examples show moving thumbnails
91+
4. Verify that static examples (explore, save_image, analyze) show custom static thumbnails
92+
93+
## Maintenance
94+
95+
When adding new animated examples:
96+
1. Create the animated GIF thumbnail (50fps, infinite loop)
97+
2. Add the GIF to `docs/_static/thumbnails/`
98+
3. Add `sphinx_gallery_thumbnail_path` comment to the example
99+
4. Update `GIF_REPLACEMENTS` dictionary in `docs/post_build.py`
100+
5. Commit all changes to version control

docs/post_build.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Post-build script to copy custom GIF thumbnails and update HTML references.
4+
5+
This script should be run after sphinx-gallery builds the documentation
6+
to replace PNG thumbnails with animated GIF thumbnails for specific examples.
7+
"""
8+
9+
import os
10+
import shutil
11+
import re
12+
13+
# Base paths
14+
DOCS_DIR = os.path.dirname(os.path.abspath(__file__))
15+
STATIC_THUMBS_DIR = os.path.join(DOCS_DIR, "_static", "thumbnails")
16+
BUILD_IMAGES_DIR = os.path.join(DOCS_DIR, "_build", "html", "_images")
17+
GALLERY_HTML = os.path.join(DOCS_DIR, "_build", "html", "auto_examples", "index.html")
18+
19+
# Mapping of PNG to GIF thumbnails that should be replaced
20+
GIF_REPLACEMENTS = {
21+
"sphx_glr_chemtrails_thumb.png": "sphx_glr_chemtrails_thumb.gif",
22+
"sphx_glr_animate_MDS_thumb.png": "sphx_glr_animate_MDS_thumb.gif",
23+
"sphx_glr_animate_spin_thumb.png": "sphx_glr_animate_spin_thumb.gif",
24+
"sphx_glr_animate_thumb.png": "sphx_glr_animate_thumb.gif",
25+
"sphx_glr_precog_thumb.png": "sphx_glr_precog_thumb.gif",
26+
"sphx_glr_save_movie_thumb.png": "sphx_glr_save_movie_thumb.gif"
27+
}
28+
29+
def copy_gif_thumbnails():
30+
"""Copy GIF thumbnails from _static/thumbnails to _build/html/_images"""
31+
print("Copying GIF thumbnails...")
32+
33+
if not os.path.exists(BUILD_IMAGES_DIR):
34+
print(f"Error: Build images directory not found: {BUILD_IMAGES_DIR}")
35+
return False
36+
37+
if not os.path.exists(STATIC_THUMBS_DIR):
38+
print(f"Error: Static thumbnails directory not found: {STATIC_THUMBS_DIR}")
39+
return False
40+
41+
# Copy all GIF files from static to build directory
42+
gif_files = [f for f in os.listdir(STATIC_THUMBS_DIR) if f.endswith('.gif')]
43+
44+
for gif_file in gif_files:
45+
src = os.path.join(STATIC_THUMBS_DIR, gif_file)
46+
dst = os.path.join(BUILD_IMAGES_DIR, gif_file)
47+
48+
shutil.copy2(src, dst)
49+
print(f" Copied: {gif_file}")
50+
51+
print(f"Copied {len(gif_files)} GIF thumbnails")
52+
return True
53+
54+
def update_html_references():
55+
"""Update HTML gallery to reference GIF files instead of PNG"""
56+
print("Updating HTML references...")
57+
58+
if not os.path.exists(GALLERY_HTML):
59+
print(f"Error: Gallery HTML not found: {GALLERY_HTML}")
60+
return False
61+
62+
# Read the HTML file
63+
with open(GALLERY_HTML, 'r', encoding='utf-8') as f:
64+
html_content = f.read()
65+
66+
# Replace PNG references with GIF references
67+
replacements_made = 0
68+
for png_name, gif_name in GIF_REPLACEMENTS.items():
69+
if png_name in html_content:
70+
html_content = html_content.replace(png_name, gif_name)
71+
replacements_made += 1
72+
print(f" Replaced: {png_name} -> {gif_name}")
73+
74+
# Write the updated HTML back
75+
with open(GALLERY_HTML, 'w', encoding='utf-8') as f:
76+
f.write(html_content)
77+
78+
print(f"Made {replacements_made} HTML replacements")
79+
return True
80+
81+
def main():
82+
"""Main function to run post-build processing"""
83+
print("Running post-build script to fix animated thumbnails...")
84+
85+
success = copy_gif_thumbnails()
86+
if success:
87+
success = update_html_references()
88+
89+
if success:
90+
print("✅ Post-build processing completed successfully!")
91+
print("Animated GIF thumbnails should now be working in the gallery.")
92+
else:
93+
print("❌ Post-build processing failed!")
94+
return 1
95+
96+
return 0
97+
98+
if __name__ == "__main__":
99+
exit(main())

0 commit comments

Comments
 (0)