forked from genewildish/Mainline
158 lines
8.4 KiB
Markdown
158 lines
8.4 KiB
Markdown
# MAINLINE
|
||
|
||
> *Digital consciousness stream. Matrix aesthetic · THX-1138 hue.*
|
||
|
||
A full-screen terminal news ticker that renders live global headlines in large OTF-font block characters with a white-hot → deep green gradient. Headlines auto-translate into the native script of their subject region. Ambient mic input warps the glitch rate in real time. A `--poetry` mode replaces the feed with public-domain literary passages. Live messages can be pushed to the display over [ntfy.sh](https://ntfy.sh).
|
||
|
||
---
|
||
|
||
## Run
|
||
|
||
```bash
|
||
python3 mainline.py # news stream
|
||
python3 mainline.py --poetry # literary consciousness mode
|
||
python3 mainline.py -p # same
|
||
python3 mainline.py --firehose # dense rapid-fire headline mode
|
||
python3 mainline.py --refresh # force re-fetch (bypass cache)
|
||
python3 mainline.py --no-font-picker # skip interactive font picker
|
||
python3 mainline.py --font-file path.otf # use a specific font file
|
||
python3 mainline.py --font-dir ~/fonts # scan a different font folder
|
||
python3 mainline.py --font-index 1 # select face index within a collection
|
||
```
|
||
|
||
First run bootstraps a local `.mainline_venv/` and installs deps (`feedparser`, `Pillow`, `sounddevice`, `numpy`). Subsequent runs start immediately, loading from cache.
|
||
|
||
---
|
||
|
||
## Config
|
||
|
||
All constants live in `engine/config.py`:
|
||
|
||
| Constant | Default | What it does |
|
||
|---|---|---|
|
||
| `HEADLINE_LIMIT` | `1000` | Total headlines per session |
|
||
| `FEED_TIMEOUT` | `10` | Per-feed HTTP timeout (seconds) |
|
||
| `MIC_THRESHOLD_DB` | `50` | dB floor above which glitches spike |
|
||
| `FONT_DIR` | `fonts/` | Folder scanned for `.otf`, `.ttf`, `.ttc` files |
|
||
| `FONT_PATH` | first file in `FONT_DIR` | Active display font (overridden by picker or `--font-file`) |
|
||
| `FONT_INDEX` | `0` | Face index within a font collection file |
|
||
| `FONT_PICKER` | `True` | Show interactive font picker at boot (`--no-font-picker` to skip) |
|
||
| `FONT_SZ` | `60` | Font render size (affects block density) |
|
||
| `RENDER_H` | `8` | Terminal rows per headline line |
|
||
| `SSAA` | `4` | Super-sampling factor (render at 4× then downsample) |
|
||
| `SCROLL_DUR` | `5.625` | Seconds per headline |
|
||
| `FRAME_DT` | `0.05` | Frame interval in seconds (20 FPS) |
|
||
| `GRAD_SPEED` | `0.08` | Gradient sweep speed (cycles/sec, ~12s full sweep) |
|
||
| `FIREHOSE_H` | `12` | Firehose zone height (terminal rows) |
|
||
| `NTFY_TOPIC` | klubhaus URL | ntfy.sh JSON endpoint to poll |
|
||
| `NTFY_POLL_INTERVAL` | `15` | Seconds between ntfy polls |
|
||
| `MESSAGE_DISPLAY_SECS` | `30` | How long an ntfy message holds the screen |
|
||
|
||
---
|
||
|
||
## Fonts
|
||
|
||
A `fonts/` directory is bundled with demo faces (AlphatronDemo, CSBishopDrawn, CyberformDemo, KATA, Microbots, Neoform, Pixel Sparta, Robocops, Xeonic, and others). On startup, an interactive picker lists all discovered faces with a live half-block preview rendered at your configured size.
|
||
|
||
Navigation: `↑`/`↓` or `j`/`k` to move, `Enter` or `q` to select. The selected face persists for that session.
|
||
|
||
To add your own fonts, drop `.otf`, `.ttf`, or `.ttc` files into `fonts/` (or point `--font-dir` at any other folder). Font collections (`.ttc`, multi-face `.otf`) are enumerated face-by-face.
|
||
|
||
---
|
||
|
||
## How it works
|
||
|
||
- On launch, the font picker scans `fonts/` and presents a live-rendered TUI for face selection; `--no-font-picker` skips directly to stream
|
||
- Feeds are fetched and filtered on startup (sports and vapid content stripped); results are cached to `.mainline_cache_news.json` / `.mainline_cache_poetry.json` for fast restarts
|
||
- Headlines are rasterized via Pillow with 4× SSAA into half-block characters (`▀▄█ `) at the configured font size
|
||
- The ticker uses a sweeping white-hot → deep green gradient; ntfy messages use a complementary white-hot → magenta/maroon gradient to distinguish them visually
|
||
- Subject-region detection runs a regex pass on each headline; matches trigger a Google Translate call and font swap to the appropriate script (CJK, Arabic, Devanagari, etc.) using macOS system fonts
|
||
- The mic stream runs in a background thread, feeding RMS dB into the glitch probability calculation each frame
|
||
- The viewport scrolls through a virtual canvas of pre-rendered blocks; fade zones at top and bottom dissolve characters probabilistically
|
||
- An ntfy.sh poller runs in a background thread; incoming messages interrupt the scroll and render full-screen until dismissed or expired
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
`mainline.py` is a thin entrypoint (venv bootstrap → `engine.app.main()`). All logic lives in the `engine/` package:
|
||
|
||
```
|
||
engine/
|
||
config.py constants, CLI flags, glyph tables
|
||
sources.py FEEDS, POETRY_SOURCES, language/script maps
|
||
terminal.py ANSI codes, tw/th, type_out, boot_ln
|
||
filter.py HTML stripping, content filter
|
||
translate.py Google Translate wrapper + region detection
|
||
render.py OTF → half-block pipeline (SSAA, gradient)
|
||
effects.py noise, glitch_bar, fade, firehose
|
||
fetch.py RSS/Gutenberg fetching + cache load/save
|
||
ntfy.py NtfyPoller — standalone, zero internal deps
|
||
mic.py MicMonitor — standalone, graceful fallback
|
||
scroll.py stream() frame loop + message rendering
|
||
app.py main(), font picker TUI, boot sequence, signal handler
|
||
```
|
||
|
||
`ntfy.py` and `mic.py` have zero internal dependencies and can be imported by any other visualizer.
|
||
|
||
---
|
||
|
||
## Feeds
|
||
|
||
~25 sources across four categories: **Science & Technology**, **Economics & Business**, **World & Politics**, **Culture & Ideas**. Add or swap feeds in `engine/sources.py` → `FEEDS`.
|
||
|
||
**Poetry mode** pulls from Project Gutenberg: Whitman, Dickinson, Thoreau, Emerson. Sources are in `engine/sources.py` → `POETRY_SOURCES`.
|
||
|
||
---
|
||
|
||
## ntfy.sh Integration
|
||
|
||
Mainline polls a configurable ntfy.sh topic in the background. When a message arrives, the scroll pauses and the message renders full-screen for `MESSAGE_DISPLAY_SECS` seconds, then the stream resumes.
|
||
|
||
To push a message:
|
||
|
||
```bash
|
||
curl -d "Body text" -H "Title: Alert title" https://ntfy.sh/your_topic
|
||
```
|
||
|
||
Update `NTFY_TOPIC` in `engine/config.py` to point at your own topic. The `NtfyPoller` class is fully standalone and can be reused by other visualizers:
|
||
|
||
```python
|
||
from engine.ntfy import NtfyPoller
|
||
poller = NtfyPoller("https://ntfy.sh/my_topic/json?since=20s&poll=1")
|
||
poller.start()
|
||
# in render loop:
|
||
msg = poller.get_active_message() # returns (title, body, timestamp) or None
|
||
```
|
||
|
||
---
|
||
|
||
## Ideas / Future
|
||
|
||
### Performance
|
||
- **Concurrent feed fetching** — startup currently blocks sequentially on ~25 HTTP requests; `concurrent.futures.ThreadPoolExecutor` would cut load time to the slowest single feed
|
||
- **Background refresh** — re-fetch feeds in a daemon thread so a long session stays current without restart
|
||
- **Translation pre-fetch** — run translate calls concurrently during the boot sequence rather than on first render
|
||
|
||
### Graphics
|
||
- **Matrix rain underlay** — katakana column rain rendered at low opacity beneath the scrolling blocks as a background layer
|
||
- **CRT simulation** — subtle dim scanlines every N rows, occasional brightness ripple across the full screen
|
||
- **Sixel / iTerm2 inline images** — bypass half-blocks entirely and stream actual bitmap frames for true resolution; would require a capable terminal
|
||
- **Parallax secondary column** — a second, dimmer, faster-scrolling stream of ambient text at reduced opacity on one side
|
||
|
||
### Cyberpunk Vibes
|
||
- **Keyword watch list** — highlight or strobe any headline matching tracked terms (names, topics, tickers)
|
||
- **Breaking interrupt** — full-screen flash + synthesized blip when a high-priority keyword hits
|
||
- **Live data overlay** — secondary ticker strip at screen edge: BTC price, ISS position, geomagnetic index
|
||
- **Theme switcher** — `--amber` (phosphor), `--ice` (electric cyan), `--red` (alert state) palette modes via CLI flag
|
||
- **Persona modes** — `--surveillance`, `--oracle`, `--underground` as feed presets with matching color themes and boot copy
|
||
- **Synthesized audio** — short static bursts tied to glitch events, independent of mic input
|
||
|
||
### Extensibility
|
||
- **serve.py** — HTTP server that imports `engine.render` and `engine.fetch` directly to stream 1-bit bitmaps to an ESP32 display
|
||
- **Rust port** — `ntfy.py` and `render.py` are the natural first targets; clear module boundaries make incremental porting viable
|
||
|
||
---
|
||
|
||
*macOS only (script/system font paths for translation are hardcoded). Primary display font is user-selectable via the bundled `fonts/` picker. Python 3.9+.*
|