Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Ensure consistent line endings
* text=auto

# Scripts must use LF
*.py text eol=lf
*.sh text eol=lf
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Weekly Dev Chat website — MkDocs Material static site. Read `mkdocs.yml` and r
- Posts are for Tuesdays (the weekly chat day). Use next Tuesday's date unless told otherwise.
- End post body with this paragraph (before the image):
`Everyone and anyone is welcome to [join](https://weeklydevchat.com/join/) as long as you are kind, supportive, and respectful of others. Zoom link will be posted at 12pm MDT.`
- Place images in the same directory as the post's `index.md`.
- Place images in the same directory as the post's `index.md`. Run `python3 scripts/optimize_image.py <image>` to convert to WebP and resize for the web.
- Use `./create_post.sh` to scaffold a new post (calculates next Tuesday automatically).
- **Multiple posts on the same date:** If a date folder already has an `index.md`, prefix the filename with a number and dash (e.g., `0-index.md`). The newest/latest post should use the lowest number so it appears first on the homepage. The original `index.md` keeps its name.

Expand Down
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,43 @@ These scripts will:

4. Add any images to the same directory as the post

5. **Optimize images** for the web before committing (see [Optimizing Images](#optimizing-images) below)

### Optimizing Images

After adding an image to a post, run the optimization script to convert it to WebP and resize it for the web:

```bash
python3 scripts/optimize_image.py docs/posts/YYYY/MM/DD/your_image.png
```

This will create an optimized `.webp` file alongside the original. Update your post's markdown to reference the new `.webp` file, then delete the original if no longer needed.

**Options:**

| Flag | Default | Description |
|------|---------|-------------|
| `--quality` | 80 | WebP quality (1–100) |
| `--max-width` | 1200 | Max width in pixels (won't upscale) |

**Examples:**

```bash
# Optimize a single image with defaults
python3 scripts/optimize_image.py docs/posts/2026/04/14/photo.png

# Batch optimize multiple images
python3 scripts/optimize_image.py docs/posts/2026/04/14/*.png docs/posts/2026/04/14/*.jpg

# Custom quality and max width
python3 scripts/optimize_image.py --quality 90 --max-width 1600 image.jpeg
```

> **Note:** This script requires [Pillow](https://pillow.readthedocs.io/). Install dev dependencies with:
> ```bash
> pip install -r requirements-dev.txt
> ```

### Categories and Tags

Categories are broad topic groupings and tags are specific topic labels for filtering. **Please use existing categories and tags when possible** to keep the taxonomy consistent. New ones can be added when truly needed.
Expand Down Expand Up @@ -145,8 +182,11 @@ This script requires `pyyaml`, which is included in `requirements.txt`. It is al
├── docker-compose.yml # Docker development environment
├── create_post.sh # Bash script to create blog posts
├── create_post.ps1 # PowerShell script to create blog posts
├── requirements-dev.in # Dev-only dependency pins (e.g. Pillow)
├── requirements-dev.txt # Compiled dev dependencies
├── scripts/
│ └── find_tags_categories.py # List all existing tags and categories
│ ├── find_tags_categories.py # List all existing tags and categories
│ └── optimize_image.py # Optimize images for the web (PNG/JPEG → WebP)
├── .github/
│ ├── dependabot.yml # Dependabot configuration
│ └── workflows/
Expand Down Expand Up @@ -176,6 +216,9 @@ mkdocs serve -a 0.0.0.0:8000 # Start server accessible on network
mkdocs build # Build static site to site/ directory
mkdocs build --clean # Clean build

# Optimize an image for the web
python3 scripts/optimize_image.py docs/posts/YYYY/MM/DD/image.png

# Help
mkdocs -h # Show help

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions docs/posts/2026/04/14/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: "What are you Working On?"
date: 2026-04-14
authors:
- chris
categories:
- Community
tags:
- open-discussion
- side-projects
- coding-practices
---

Today's chat (2026-04-14) is an opportunity to show what you have been working. Give us a quick explanation, and if time permits, a quick demo. By quick I mean 5 minutes or less. The actual time will be driven by how many people want to show off.

Anything software or technical is allowed. Some examples:

- a product you are working on
- some cool code you wrote
- an interesting problem you recently solved

Looking forward to hearing what everyone has been working on lately!

Everyone and anyone are welcome to [join](https://weeklydevchat.com/join/) as long as you are kind, supportive, and respectful of others. Zoom link will be posted at 12pm MDT.

*Featured image was created with Nano Banana using this post as the prompt. It is both amazing and creepy at the same time.

![Featured image showing a presenter demoing their product](2026-04-14_featured_image.webp)
4 changes: 4 additions & 0 deletions requirements-dev.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Dev-only dependencies (not needed for production builds)
# Regenerate with: uv pip compile requirements-dev.in --output-file requirements-dev.txt

Pillow>=10.0.0
4 changes: 4 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements-dev.in --output-file requirements-dev.txt
pillow==11.3.0
# via -r requirements-dev.in
142 changes: 142 additions & 0 deletions scripts/optimize_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
Optimize images for the Weekly Dev Chat website.

Converts images to WebP format, resizes to a max width, and reports savings.
The original file is preserved; the optimized WebP is written alongside it.

Usage:
python scripts/optimize_image.py image1.png image2.jpg
python scripts/optimize_image.py --quality 85 --max-width 1600 image.png
"""

import argparse
import sys

if sys.version_info < (3, 14):
print(
f"Error: Python 3.14 or later is required (running {sys.version}).",
file=sys.stderr,
)
sys.exit(1)

from pathlib import Path

try:
from PIL import Image, ImageOps
except ImportError:
print(
"Error: Pillow is not installed.\n"
"Install dev dependencies with:\n"
" pip install -r requirements-dev.txt\n"
"Or install Pillow directly:\n"
" pip install Pillow",
file=sys.stderr,
)
sys.exit(1)

SUPPORTED_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".tif"}


def _quality_int(value: str) -> int:
"""Argparse type for --quality: integer in range 1–100."""
v = int(value)
if not (1 <= v <= 100):
raise argparse.ArgumentTypeError(f"quality must be between 1 and 100 (got {v})")
return v


def _positive_int(value: str) -> int:
"""Argparse type for --max-width: integer greater than 0."""
v = int(value)
if v <= 0:
raise argparse.ArgumentTypeError(f"max-width must be greater than 0 (got {v})")
return v


def optimize_image(input_path: Path, *, quality: int, max_width: int) -> Path | None:
"""Optimize a single image. Returns the output path, or None on error."""
Comment thread
mrbiggred marked this conversation as resolved.
if input_path.suffix.lower() not in SUPPORTED_EXTENSIONS:
print(f" Skipping {input_path.name}: unsupported format")
return None

try:
img = Image.open(input_path)
Comment thread
normanlorrain marked this conversation as resolved.
except Exception as e:
print(f" Error opening {input_path.name}: {e}", file=sys.stderr)
return None

# Apply EXIF orientation so JPEGs rotated via metadata are correctly oriented
img = ImageOps.exif_transpose(img)

# Convert palette/RGBA images appropriately for WebP
if img.mode in ("P", "PA"):
img = img.convert("RGBA")
elif img.mode not in ("RGB", "RGBA"):
img = img.convert("RGB")

# Resize if wider than max_width, preserving aspect ratio
if img.width > max_width:
ratio = max_width / img.width
new_height = round(img.height * ratio)
img = img.resize((max_width, new_height), Image.LANCZOS)
Comment thread
mrbiggred marked this conversation as resolved.

output_path = input_path.with_suffix(".webp")
img.save(output_path, "WEBP", quality=quality)

original_size = input_path.stat().st_size
optimized_size = output_path.stat().st_size
change_pct = (optimized_size / original_size - 1) * 100 if original_size > 0 else 0
Comment thread
mrbiggred marked this conversation as resolved.

print(f" {input_path.name}")
print(f" {original_size:,} bytes → {optimized_size:,} bytes ({change_pct:+.1f}%)")
print(f" Saved to {output_path.name} ({img.width}x{img.height})")

return output_path


def main() -> None:
parser = argparse.ArgumentParser(
description="Optimize images for the Weekly Dev Chat website.",
)
parser.add_argument(
"images",
nargs="+",
type=Path,
help="One or more image file paths to optimize.",
)
parser.add_argument(
"--quality",
type=_quality_int,
default=80,
help="WebP quality (1-100, default: 80).",
)
parser.add_argument(
"--max-width",
type=_positive_int,
default=1200,
help="Max image width in pixels (default: 1200). Images smaller than this are not upscaled.",
)
Comment thread
mrbiggred marked this conversation as resolved.
args = parser.parse_args()

successes = 0
failures = 0

for image_path in args.images:
if not image_path.is_file():
print(f" Warning: {image_path} not found, skipping.", file=sys.stderr)
failures += 1
continue

result = optimize_image(image_path, quality=args.quality, max_width=args.max_width)
if result:
successes += 1
else:
failures += 1

print(f"\nDone: {successes} optimized, {failures} skipped/failed.")
sys.exit(1 if failures > 0 and successes == 0 else 0)


if __name__ == "__main__":
main()
Loading