A Celestial Time-Lapse Generator for the Command Line
NightWindow is a Python-based CLI application that allows users to peer into the night sky from any location on Earth. By providing a city name or using their current IP, users can generate a beautiful, animated ascii GIF showing the movement of stars, planets, and the moon over the course of a night.
Initially, the project was intended to use external Astronomy APIs. However, after realizing that third-party image outputs lacked the detail and customization needed for a smooth time-lapse, I shifted to a local rendering approach.
- Skyfield: Used to calculate precise celestial positions using the DE421 planetary ephemeris and the Hipparcos star catalog.
- Matplotlib: Acts as the rendering heart, plotting thousands of stars with magnitudes and colors, along with the Moon and visible planets (Mars, Jupiter, Saturn, Venus).
- ASCII Magic: Integrated to provide a unique "retro" aesthetic to the generated frames before compiling them.
While Matplotlib is traditionally used for data visualization, NightWindow repurposes it as a graphical render engine to generate high-fidelity celestial frames.
Key technical implementations include:
-
Dynamic Star Sizing: Instead of uniform points, star sizes are calculated using an exponential formula based on their Hipparcos magnitude. This mimics real-world luminosity, where brighter stars appear larger on a black background.
- Formula:
s = ((6 - magnitude) ** 2.5) + 20
- Formula:
-
Horizon Masking: The engine performs real-time filtering of astronomical coordinates. Only objects with an altitude >0° are passed to the scatter plot, ensuring stars and planets "set" naturally behind the horizon.
-
Planetary Color Mapping: Specific hex-color values are assigned to major celestial bodies (e.g., #ff4500 for Mars, #f59aff for Jupiter) to maintain visual clarity and scientific recognition in the final output.
-
Fixed Viewport Configuration: To create a consistent time-lapse, the engine locks the field of view (FOV) and axes limits. This prevents Matplotlib from auto-scaling, which is crucial for achieving a stable video effect in the final GIF.
-
Headless Rendering: The class is optimized to run without a GUI backend (
plt.close()), allowing for the rapid generation of hundreds of frames in the background without opening floating windows.
- Threaded Spinner: To prevent the CLI from feeling "frozen" during heavy astronomical calculations or API calls, I implemented a custom Spinner class. It runs on a background thread (threading), allowing a Braille animation to spin smoothly while the main thread processes data.
- Custom Decorators: I developed the
@with_spinnerdecorator to wrap heavy-lifting functions. This keeps the code clean and provides a consistent visual feedback system (Loading → Success). - Single-Key Navigation: Using the readchar library, the menu system responds to instant keypresses, eliminating the need for the user to press "Enter" after every selection.
One of the most significant technical challenges was managing the project's growth. As I modularized the code into project.py, utils.py, and skysimulator.py, I encountered a circular import error—the main script needed the utilities, but the utilities needed the global configuration and language strings.
The Solution: I refactored the architecture by creating a dedicated config.py. This file acts as a "Single Source of Truth," holding global constants, ASCII art, and the JSON-based internationalization system. This linearized the dependency graph and resolved the conflict.
- project.py: The main execution flow and UI logic.
- skysimulator.py: The object-oriented engine for astronomy calculations and frame generation.
- utils.py: Reusable logic for the CLI (Spinners, decorators, file management).
- config.py: Global state and asset loading.
- en_texts.json: Centralized text dictionary for easy maintenance and future localization.
- Python 3.10+
- Dependencies (install via
pip install -r requirements.txt):- skyfield
- matplotlib
- requests
- readchar
- geopy
- pillow
- ascii_magic
- Navigate to the project directory.
- Run the application:
python project.py
- Choose your location method (IP or City).
- Confirm the summary and wait for the "SkyEngine" to compile your night window.
- Find your generated GIF in the root folder with the format:
nightwindow_YYYYMMDD_city.gif.
This project allowed me to solidify my knowledge of Python's advanced features, particularly Multithreading and Decorators. Exploring the "Skeleton UX" flow helped me understand how a user interacts with a program, ensuring that even during long processing times, the interface remains engaging and informative. I truly enjoyed building a tool that turns cold, scientific data into a visual experience.
- Skyfield - Astronomical position calculations and star catalog
- Hipparcos Catalog - Comprehensive star data
- ASCII Magic - ASCII art conversion
- CS50P Staff - Foundations of Python development
Enjoy your window to the stars! 🔭✨
