Switch monthly phase close-ups to NASA SVS Dial-a-Moon at fullscreen
The previous east-flavored composite (tight-crop east region + lunar texture overlay) was honest but visually limited: 38-px source moon, no real terminator shadows on quarters, and a faint upscaled-halo background that was less compelling than just a clean lunar render. New flow: - East stays the witness (verifies the moon was visible during the collection window) and supplies the timestamp. - moon_dialamoon.py fetches the NASA SVS Dial-a-Moon render for that exact UTC hour. Free, public-domain, real-physics: correct phase, libration, and crater shadows for any timestamp. - moon_composite.render_phase_closeup() places that render at ~92% of frame height on a 1920x1080 black background -- the long-telephoto look the user asked for. - One API call per phase event (~36/year), cached forever in moon-ref/dialamoon/. Quarter moons now show real 3D crater shadows along the terminator, which the prior algorithmic phase-shadow couldn't simulate from a full-moon reference. Adds MOON_REQUIRE_EAST_VERIFY=true|false toggle so the user can choose "only post when east saw it" (default, current behavior) vs "post every cycle regardless of weather over east." Drops the install-time lunar reference download (no longer needed) and the now-unused per-phase phase-shadow path stays in moon_composite.py for reference / future reuse. https://claude.ai/code/session_015PBVDESC3KLMbq1LpA6qLn
This commit is contained in:
@@ -209,6 +209,47 @@ def composite_full_moon(
|
||||
return out_path
|
||||
|
||||
|
||||
def render_phase_closeup(
|
||||
nasa_render_path: str,
|
||||
out_path: str,
|
||||
output_size: tuple[int, int] = (1920, 1080),
|
||||
moon_height_pct: float = 0.92,
|
||||
caption: str | None = None,
|
||||
background: tuple[int, int, int] = (0, 0, 0),
|
||||
):
|
||||
"""Full-screen close-up rendering using a NASA SVS Dial-a-Moon image.
|
||||
|
||||
The dial-a-moon render already has the correct phase, libration and
|
||||
crater shadows for the requested timestamp, so we simply size it to fill
|
||||
the output frame on a black background and add a caption. No east
|
||||
compositing — the moon dominates the frame the way a 65× telephoto shot
|
||||
would.
|
||||
"""
|
||||
bg = Image.new('RGB', output_size, background)
|
||||
moon = Image.open(nasa_render_path).convert('RGB')
|
||||
moon = _square_crop_to_disk(moon)
|
||||
|
||||
target = int(round(output_size[1] * moon_height_pct))
|
||||
target += target % 2
|
||||
moon_resized = moon.resize((target, target), LANCZOS)
|
||||
|
||||
feather = max(3, target // 240)
|
||||
mask = _disk_mask(target, feather_px=feather)
|
||||
|
||||
px = (output_size[0] - target) // 2
|
||||
py = (output_size[1] - target) // 2
|
||||
bg.paste(moon_resized, (px, py), mask)
|
||||
|
||||
if caption:
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text((24, output_size[1] - 44), caption, fill=(0, 0, 0))
|
||||
draw.text((22, output_size[1] - 46), caption, fill=(220, 220, 220))
|
||||
|
||||
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
bg.save(out_path, quality=92)
|
||||
return out_path
|
||||
|
||||
|
||||
def _cli():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('source')
|
||||
|
||||
Reference in New Issue
Block a user