""" Application orchestrator — boot sequence, signal handling, main loop wiring. """ import atexit import os import signal import sys import termios import time import tty from engine import config, render from engine.controller import StreamController from engine.fetch import fetch_all, fetch_poetry, load_cache, save_cache from engine.terminal import ( CLR, CURSOR_OFF, CURSOR_ON, G_DIM, G_HI, G_MID, RST, W_DIM, W_GHOST, boot_ln, slow_print, tw, ) TITLE = [ " ███╗ ███╗ █████╗ ██╗███╗ ██╗██╗ ██╗███╗ ██╗███████╗", " ████╗ ████║██╔══██╗██║████╗ ██║██║ ██║████╗ ██║██╔════╝", " ██╔████╔██║███████║██║██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ ", " ██║╚██╔╝██║██╔══██║██║██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ ", " ██║ ╚═╝ ██║██║ ██║██║██║ ╚████║███████╗██║██║ ╚████║███████╗", " ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝", ] def _read_picker_key(): ch = sys.stdin.read(1) if ch == "\x03": return "interrupt" if ch in ("\r", "\n"): return "enter" if ch == "\x1b": c1 = sys.stdin.read(1) if c1 != "[": return None c2 = sys.stdin.read(1) if c2 == "A": return "up" if c2 == "B": return "down" return None if ch in ("k", "K"): return "up" if ch in ("j", "J"): return "down" if ch in ("q", "Q"): return "enter" return None def _normalize_preview_rows(rows): """Trim shared left padding and trailing spaces for stable on-screen previews.""" non_empty = [r for r in rows if r.strip()] if not non_empty: return [""] left_pad = min(len(r) - len(r.lstrip(" ")) for r in non_empty) out = [] for row in rows: if left_pad < len(row): out.append(row[left_pad:].rstrip()) else: out.append(row.rstrip()) return out def _draw_font_picker(faces, selected): w = tw() h = 24 try: h = os.get_terminal_size().lines except Exception: pass max_preview_w = max(24, w - 8) header_h = 6 footer_h = 3 preview_h = max(4, min(config.RENDER_H + 2, max(4, h // 2))) visible = max(1, h - header_h - preview_h - footer_h) top = max(0, selected - (visible // 2)) bottom = min(len(faces), top + visible) top = max(0, bottom - visible) print(CLR, end="") print(CURSOR_OFF, end="") print() print(f" {G_HI}FONT PICKER{RST}") print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print(f" {W_DIM}{config.FONT_DIR[:max_preview_w]}{RST}") print(f" {W_GHOST}↑/↓ move · Enter select · q accept current{RST}") print() for pos in range(top, bottom): face = faces[pos] active = pos == selected pointer = "▶" if active else " " color = G_HI if active else W_DIM print( f" {color}{pointer} {face['name']}{RST}{W_GHOST} · {face['file_name']}{RST}" ) if top > 0: print(f" {W_GHOST}… {top} above{RST}") if bottom < len(faces): print(f" {W_GHOST}… {len(faces) - bottom} below{RST}") print() print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print( f" {W_DIM}Preview: {faces[selected]['name']} · {faces[selected]['file_name']}{RST}" ) preview_rows = faces[selected]["preview_rows"][:preview_h] for row in preview_rows: shown = row[:max_preview_w] print(f" {shown}") def pick_font_face(): """Interactive startup picker for selecting a face from repo OTF files.""" if not config.FONT_PICKER: return font_files = config.list_repo_font_files() if not font_files: print(CLR, end="") print(CURSOR_OFF, end="") print() print(f" {G_HI}FONT PICKER{RST}") print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") print(f" {G_DIM}> no .otf/.ttf/.ttc files found in: {config.FONT_DIR}{RST}") print(f" {W_GHOST}> add font files to the fonts folder, then rerun{RST}") time.sleep(1.8) sys.exit(1) prepared = [] for font_path in font_files: try: faces = render.list_font_faces(font_path, max_faces=64) except Exception: fallback = os.path.splitext(os.path.basename(font_path))[0] faces = [{"index": 0, "name": fallback}] for face in faces: idx = face["index"] name = face["name"] file_name = os.path.basename(font_path) try: fnt = render.load_font_face(font_path, idx) rows = _normalize_preview_rows(render.render_line(name, fnt)) except Exception: rows = ["(preview unavailable)"] prepared.append( { "font_path": font_path, "font_index": idx, "name": name, "file_name": file_name, "preview_rows": rows, } ) if not prepared: print(CLR, end="") print(CURSOR_OFF, end="") print() print(f" {G_HI}FONT PICKER{RST}") print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") print(f" {G_DIM}> no readable font faces found in: {config.FONT_DIR}{RST}") time.sleep(1.8) sys.exit(1) def _same_path(a, b): try: return os.path.samefile(a, b) except Exception: return os.path.abspath(a) == os.path.abspath(b) selected = next( ( i for i, f in enumerate(prepared) if _same_path(f["font_path"], config.FONT_PATH) and f["font_index"] == config.FONT_INDEX ), 0, ) if not sys.stdin.isatty(): selected_font = prepared[selected] config.set_font_selection( font_path=selected_font["font_path"], font_index=selected_font["font_index"], ) render.clear_font_cache() print( f" {G_DIM}> using {selected_font['name']} ({selected_font['file_name']}){RST}" ) time.sleep(0.8) print(CLR, end="") print(CURSOR_OFF, end="") print() return fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setcbreak(fd) while True: _draw_font_picker(prepared, selected) key = _read_picker_key() if key == "up": selected = max(0, selected - 1) elif key == "down": selected = min(len(prepared) - 1, selected + 1) elif key == "enter": break elif key == "interrupt": raise KeyboardInterrupt finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) selected_font = prepared[selected] config.set_font_selection( font_path=selected_font["font_path"], font_index=selected_font["font_index"], ) render.clear_font_cache() print( f" {G_DIM}> using {selected_font['name']} ({selected_font['file_name']}){RST}" ) time.sleep(0.8) print(CLR, end="") print(CURSOR_OFF, end="") print() def pick_effects_config(): """Interactive picker for configuring effects pipeline.""" import effects_plugins from engine.effects import get_effect_chain, get_registry effects_plugins.discover_plugins() registry = get_registry() chain = get_effect_chain() chain.set_order(["noise", "fade", "glitch", "firehose"]) effects = list(registry.list_all().values()) if not effects: return selected = 0 editing_intensity = False intensity_value = 1.0 def _draw_effects_picker(): w = tw() print(CLR, end="") print("\033[1;1H", end="") print(" \033[1;38;5;231mEFFECTS CONFIG\033[0m") print(f" \033[2;38;5;37m{'─' * (w - 4)}\033[0m") print() for i, effect in enumerate(effects): prefix = " > " if i == selected else " " marker = "[*]" if effect.config.enabled else "[ ]" if editing_intensity and i == selected: print( f"{prefix}{marker} \033[1;38;5;82m{effect.name}\033[0m: intensity={intensity_value:.2f} (use +/- to adjust, Enter to confirm)" ) else: print( f"{prefix}{marker} {effect.name}: intensity={effect.config.intensity:.2f}" ) print() print(f" \033[2;38;5;37m{'─' * (w - 4)}\033[0m") print( " \033[38;5;245mControls: space=toggle on/off | +/-=adjust intensity | arrows=move | Enter=next effect | q=done\033[0m" ) def _read_effects_key(): ch = sys.stdin.read(1) if ch == "\x03": return "interrupt" if ch in ("\r", "\n"): return "enter" if ch == " ": return "toggle" if ch == "q": return "quit" if ch == "+" or ch == "=": return "up" if ch == "-" or ch == "_": return "down" if ch == "\x1b": c1 = sys.stdin.read(1) if c1 != "[": return None c2 = sys.stdin.read(1) if c2 == "A": return "up" if c2 == "B": return "down" return None return None if not sys.stdin.isatty(): return fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setcbreak(fd) while True: _draw_effects_picker() key = _read_effects_key() if key == "quit" or key == "enter": break elif key == "up" and editing_intensity: intensity_value = min(1.0, intensity_value + 0.1) effects[selected].config.intensity = intensity_value elif key == "down" and editing_intensity: intensity_value = max(0.0, intensity_value - 0.1) effects[selected].config.intensity = intensity_value elif key == "up": selected = max(0, selected - 1) intensity_value = effects[selected].config.intensity elif key == "down": selected = min(len(effects) - 1, selected + 1) intensity_value = effects[selected].config.intensity elif key == "toggle": effects[selected].config.enabled = not effects[selected].config.enabled elif key == "interrupt": raise KeyboardInterrupt finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) def run_demo_mode(): """Run demo mode - showcases effects and camera modes with real content. .. deprecated:: This is legacy code. Use run_pipeline_mode() instead. """ import warnings warnings.warn( "run_demo_mode is deprecated. Use run_pipeline_mode() instead.", DeprecationWarning, stacklevel=2, ) import random from engine import config from engine.camera import Camera, CameraMode from engine.display import DisplayRegistry from engine.effects import ( EffectContext, PerformanceMonitor, get_effect_chain, get_registry, set_monitor, ) from engine.fetch import fetch_all, fetch_poetry, load_cache from engine.scroll import calculate_scroll_step print(" \033[1;38;5;46mMAINLINE DEMO MODE\033[0m") print(" \033[38;5;245mInitializing...\033[0m") import effects_plugins effects_plugins.discover_plugins() registry = get_registry() chain = get_effect_chain() chain.set_order(["noise", "fade", "glitch", "firehose", "hud"]) monitor = PerformanceMonitor() set_monitor(monitor) chain._monitor = monitor display = DisplayRegistry.create("pygame") if not display: print(" \033[38;5;196mFailed to create pygame display\033[0m") sys.exit(1) w, h = 80, 24 display.init(w, h) display.clear() print(" \033[38;5;245mFetching content...\033[0m") cached = load_cache() if cached: items = cached elif config.MODE == "poetry": items, _, _ = fetch_poetry() else: items, _, _ = fetch_all() if not items: print(" \033[38;5;196mNo content available\033[0m") sys.exit(1) random.shuffle(items) pool = list(items) seen = set() active = [] ticker_next_y = 0 noise_cache = {} scroll_motion_accum = 0.0 frame_number = 0 GAP = 3 scroll_step_interval = calculate_scroll_step(config.SCROLL_DUR, h) camera = Camera.vertical(speed=1.0) effects_to_demo = ["noise", "fade", "glitch", "firehose"] effect_idx = 0 effect_name = effects_to_demo[effect_idx] effect_start_time = time.time() current_intensity = 0.0 ramping_up = True camera_modes = [ (CameraMode.VERTICAL, "vertical"), (CameraMode.HORIZONTAL, "horizontal"), (CameraMode.OMNI, "omni"), (CameraMode.FLOATING, "floating"), ] camera_mode_idx = 0 camera_start_time = time.time() print(" \033[38;5;82mStarting effect & camera demo...\033[0m") print(" \033[38;5;245mPress Ctrl+C to exit\033[0m\n") try: while True: elapsed = time.time() - effect_start_time camera_elapsed = time.time() - camera_start_time duration = config.DEMO_EFFECT_DURATION if elapsed >= duration: effect_idx = (effect_idx + 1) % len(effects_to_demo) effect_name = effects_to_demo[effect_idx] effect_start_time = time.time() elapsed = 0 current_intensity = 0.0 ramping_up = True if camera_elapsed >= duration * 2: camera_mode_idx = (camera_mode_idx + 1) % len(camera_modes) mode, mode_name = camera_modes[camera_mode_idx] camera = Camera(mode=mode, speed=1.0) camera_start_time = time.time() camera_elapsed = 0 progress = elapsed / duration if ramping_up: current_intensity = progress if progress >= 1.0: ramping_up = False else: current_intensity = 1.0 - progress for effect in registry.list_all().values(): if effect.name == effect_name: effect.config.enabled = True effect.config.intensity = current_intensity elif effect.name not in ("hud",): effect.config.enabled = False hud_effect = registry.get("hud") if hud_effect: mode_name = camera_modes[camera_mode_idx][1] hud_effect.config.params["display_effect"] = ( f"{effect_name} / {mode_name}" ) hud_effect.config.params["display_intensity"] = current_intensity scroll_motion_accum += config.FRAME_DT while scroll_motion_accum >= scroll_step_interval: scroll_motion_accum -= scroll_step_interval camera.update(config.FRAME_DT) while ticker_next_y < camera.y + h + 10 and len(active) < 50: from engine.effects import next_headline from engine.render import make_block t, src, ts = next_headline(pool, items, seen) ticker_content, hc, midx = make_block(t, src, ts, w) active.append((ticker_content, hc, ticker_next_y, midx)) ticker_next_y += len(ticker_content) + GAP active = [ (c, hc, by, mi) for c, hc, by, mi in active if by + len(c) > camera.y ] for k in list(noise_cache): if k < camera.y: del noise_cache[k] grad_offset = (time.time() * config.GRAD_SPEED) % 1.0 from engine.layers import render_ticker_zone buf, noise_cache = render_ticker_zone( active, scroll_cam=camera.y, camera_x=camera.x, ticker_h=h, w=w, noise_cache=noise_cache, grad_offset=grad_offset, ) from engine.layers import render_firehose firehose_buf = render_firehose(items, w, 0, h) buf.extend(firehose_buf) ctx = EffectContext( terminal_width=w, terminal_height=h, scroll_cam=camera.y, ticker_height=h, camera_x=camera.x, mic_excess=0.0, grad_offset=grad_offset, frame_number=frame_number, has_message=False, items=items, ) result = chain.process(buf, ctx) display.show(result) new_w, new_h = display.get_dimensions() if new_w != w or new_h != h: w, h = new_w, new_h scroll_step_interval = calculate_scroll_step(config.SCROLL_DUR, h) active = [] noise_cache = {} frame_number += 1 time.sleep(1 / 60) except KeyboardInterrupt: pass finally: display.cleanup() print("\n \033[38;5;245mDemo ended\033[0m") def run_pipeline_demo(): """Run pipeline visualization demo mode - shows ASCII pipeline animation. .. deprecated:: This demo mode uses legacy rendering. Use run_pipeline_mode() instead. """ import warnings warnings.warn( "run_pipeline_demo is deprecated. Use run_pipeline_mode() instead.", DeprecationWarning, stacklevel=2, ) import time from engine import config from engine.camera import Camera, CameraMode from engine.display import DisplayRegistry from engine.effects import ( EffectContext, PerformanceMonitor, get_effect_chain, get_registry, set_monitor, ) from engine.pipeline_viz import generate_large_network_viewport print(" \033[1;38;5;46mMAINLINE PIPELINE DEMO\033[0m") print(" \033[38;5;245mInitializing...\033[0m") import effects_plugins effects_plugins.discover_plugins() registry = get_registry() chain = get_effect_chain() chain.set_order(["noise", "fade", "glitch", "firehose", "hud"]) monitor = PerformanceMonitor() set_monitor(monitor) chain._monitor = monitor display = DisplayRegistry.create("pygame") if not display: print(" \033[38;5;196mFailed to create pygame display\033[0m") sys.exit(1) w, h = 80, 24 display.init(w, h) display.clear() camera = Camera.vertical(speed=1.0) effects_to_demo = ["noise", "fade", "glitch", "firehose"] effect_idx = 0 effect_name = effects_to_demo[effect_idx] effect_start_time = time.time() current_intensity = 0.0 ramping_up = True camera_modes = [ (CameraMode.VERTICAL, "vertical"), (CameraMode.HORIZONTAL, "horizontal"), (CameraMode.OMNI, "omni"), (CameraMode.FLOATING, "floating"), ] camera_mode_idx = 0 camera_start_time = time.time() frame_number = 0 print(" \033[38;5;82mStarting pipeline visualization...\033[0m") print(" \033[38;5;245mPress Ctrl+C to exit\033[0m\n") try: while True: elapsed = time.time() - effect_start_time camera_elapsed = time.time() - camera_start_time duration = config.DEMO_EFFECT_DURATION if elapsed >= duration: effect_idx = (effect_idx + 1) % len(effects_to_demo) effect_name = effects_to_demo[effect_idx] effect_start_time = time.time() elapsed = 0 current_intensity = 0.0 ramping_up = True if camera_elapsed >= duration * 2: camera_mode_idx = (camera_mode_idx + 1) % len(camera_modes) mode, mode_name = camera_modes[camera_mode_idx] camera = Camera(mode=mode, speed=1.0) camera_start_time = time.time() camera_elapsed = 0 progress = elapsed / duration if ramping_up: current_intensity = progress if progress >= 1.0: ramping_up = False else: current_intensity = 1.0 - progress for effect in registry.list_all().values(): if effect.name == effect_name: effect.config.enabled = True effect.config.intensity = current_intensity elif effect.name not in ("hud",): effect.config.enabled = False hud_effect = registry.get("hud") if hud_effect: mode_name = camera_modes[camera_mode_idx][1] hud_effect.config.params["display_effect"] = ( f"{effect_name} / {mode_name}" ) hud_effect.config.params["display_intensity"] = current_intensity camera.update(config.FRAME_DT) buf = generate_large_network_viewport(w, h, frame_number) ctx = EffectContext( terminal_width=w, terminal_height=h, scroll_cam=camera.y, ticker_height=h, camera_x=camera.x, mic_excess=0.0, grad_offset=0.0, frame_number=frame_number, has_message=False, items=[], ) result = chain.process(buf, ctx) display.show(result) new_w, new_h = display.get_dimensions() if new_w != w or new_h != h: w, h = new_w, new_h frame_number += 1 time.sleep(1 / 60) except KeyboardInterrupt: pass finally: display.cleanup() print("\n \033[38;5;245mPipeline demo ended\033[0m") def run_preset_mode(preset_name: str): """Run mode using animation presets. .. deprecated:: Use run_pipeline_mode() with preset parameter instead. """ import warnings warnings.warn( "run_preset_mode is deprecated. Use run_pipeline_mode() instead.", DeprecationWarning, stacklevel=2, ) from engine import config from engine.animation import ( create_demo_preset, create_pipeline_preset, ) from engine.camera import Camera from engine.display import DisplayRegistry from engine.effects import ( EffectContext, PerformanceMonitor, get_effect_chain, get_registry, set_monitor, ) from engine.sources_v2 import ( PipelineDataSource, get_source_registry, init_default_sources, ) w, h = 80, 24 if preset_name == "demo": preset = create_demo_preset() init_default_sources() source = get_source_registry().default() elif preset_name == "pipeline": preset = create_pipeline_preset() source = PipelineDataSource(w, h) else: print(f" \033[38;5;196mUnknown preset: {preset_name}\033[0m") print(" Available: demo, pipeline") sys.exit(1) print(f" \033[1;38;5;46mMAINLINE PRESET: {preset.name}\033[0m") print(f" \033[38;5;245m{preset.description}\033[0m") print(" \033[38;5;245mInitializing...\033[0m") import effects_plugins effects_plugins.discover_plugins() registry = get_registry() chain = get_effect_chain() chain.set_order(["noise", "fade", "glitch", "firehose", "hud"]) monitor = PerformanceMonitor() set_monitor(monitor) chain._monitor = monitor display = DisplayRegistry.create(preset.initial_params.display_backend) if not display: print( f" \033[38;5;196mFailed to create {preset.initial_params.display_backend} display\033[0m" ) sys.exit(1) display.init(w, h) display.clear() camera = Camera.vertical() print(" \033[38;5;82mStarting preset animation...\033[0m") print(" \033[38;5;245mPress Ctrl+C to exit\033[0m\n") controller = preset.create_controller() frame_number = 0 try: while True: params = controller.update() effect_name = params.get("current_effect", "none") intensity = params.get("effect_intensity", 0.0) camera_mode = params.get("camera_mode", "vertical") if camera_mode == "vertical": camera = Camera.vertical(speed=params.get("camera_speed", 1.0)) elif camera_mode == "horizontal": camera = Camera.horizontal(speed=params.get("camera_speed", 1.0)) elif camera_mode == "omni": camera = Camera.omni(speed=params.get("camera_speed", 1.0)) elif camera_mode == "floating": camera = Camera.floating(speed=params.get("camera_speed", 1.0)) camera.update(config.FRAME_DT) for eff in registry.list_all().values(): if eff.name == effect_name: eff.config.enabled = True eff.config.intensity = intensity elif eff.name not in ("hud",): eff.config.enabled = False hud_effect = registry.get("hud") if hud_effect: hud_effect.config.params["display_effect"] = ( f"{effect_name} / {camera_mode}" ) hud_effect.config.params["display_intensity"] = intensity source.viewport_width = w source.viewport_height = h items = source.get_items() buffer = items[0].content.split("\n") if items else [""] * h ctx = EffectContext( terminal_width=w, terminal_height=h, scroll_cam=camera.y, ticker_height=h, camera_x=camera.x, mic_excess=0.0, grad_offset=0.0, frame_number=frame_number, has_message=False, items=[], ) result = chain.process(buffer, ctx) display.show(result) new_w, new_h = display.get_dimensions() if new_w != w or new_h != h: w, h = new_w, new_h frame_number += 1 time.sleep(1 / 60) except KeyboardInterrupt: pass finally: display.cleanup() print("\n \033[38;5;245mPreset ended\033[0m") def main(): from engine import config from engine.pipeline import list_presets # Show pipeline diagram if requested if config.PIPELINE_DIAGRAM: try: from engine.pipeline import generate_pipeline_diagram except ImportError: print("Error: pipeline diagram not available") return print(generate_pipeline_diagram()) return # Unified preset-based entry point # All modes are now just presets preset_name = None # Check for --preset flag first if config.PRESET: preset_name = config.PRESET # Check for legacy --pipeline flag (mapped to demo preset) elif config.PIPELINE_MODE: preset_name = config.PIPELINE_PRESET # Default to demo if no preset specified else: preset_name = "demo" # Validate preset exists available = list_presets() if preset_name not in available: print(f"Error: Unknown preset '{preset_name}'") print(f"Available presets: {', '.join(available)}") sys.exit(1) # Run with the selected preset run_pipeline_mode(preset_name) atexit.register(lambda: print(CURSOR_ON, end="", flush=True)) def handle_sigint(*_): print(f"\n\n {G_DIM}> SIGNAL LOST{RST}") print(f" {W_GHOST}> connection terminated{RST}\n") sys.exit(0) signal.signal(signal.SIGINT, handle_sigint) StreamController.warmup_topics() w = tw() print(CLR, end="") print(CURSOR_OFF, end="") pick_font_face() pick_effects_config() w = tw() print() time.sleep(0.4) for ln in TITLE: print(f"{G_HI}{ln}{RST}") time.sleep(0.07) print() _subtitle = ( "literary consciousness stream" if config.MODE == "poetry" else "digital consciousness stream" ) print(f" {W_DIM}v0.1 · {_subtitle}{RST}") print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print() time.sleep(0.4) cached = load_cache() if "--refresh" not in sys.argv else None if cached: items = cached boot_ln("Cache", f"LOADED [{len(items)} SIGNALS]", True) elif config.MODE == "poetry": slow_print(" > INITIALIZING LITERARY CORPUS...\n") time.sleep(0.2) print() items, linked, failed = fetch_poetry() print() print( f" {G_DIM}>{RST} {G_MID}{linked} TEXTS LOADED{RST} {W_GHOST}· {failed} DARK{RST}" ) print(f" {G_DIM}>{RST} {G_MID}{len(items)} STANZAS ACQUIRED{RST}") save_cache(items) else: slow_print(" > INITIALIZING FEED ARRAY...\n") time.sleep(0.2) print() items, linked, failed = fetch_all() print() print( f" {G_DIM}>{RST} {G_MID}{linked} SOURCES LINKED{RST} {W_GHOST}· {failed} DARK{RST}" ) print(f" {G_DIM}>{RST} {G_MID}{len(items)} SIGNALS ACQUIRED{RST}") save_cache(items) if not items: print(f"\n {W_DIM}> NO SIGNAL — check network{RST}") sys.exit(1) print() controller = StreamController() mic_ok, ntfy_ok = controller.initialize_sources() if controller.mic and controller.mic.available: boot_ln( "Microphone", "ACTIVE" if mic_ok else "OFFLINE · check System Settings → Privacy → Microphone", bool(mic_ok), ) boot_ln("ntfy", "LISTENING" if ntfy_ok else "OFFLINE", ntfy_ok) if config.FIREHOSE: boot_ln("Firehose", "ENGAGED", True) time.sleep(0.4) slow_print(" > STREAMING...\n") time.sleep(0.2) print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print() time.sleep(0.4) controller.run(items) print() print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") print(f" {G_DIM}> {config.HEADLINE_LIMIT} SIGNALS PROCESSED{RST}") print(f" {W_GHOST}> end of stream{RST}") print() def run_pipeline_mode(preset_name: str = "demo"): """Run using the new unified pipeline architecture.""" import effects_plugins from engine.display import DisplayRegistry from engine.effects import PerformanceMonitor, get_registry, set_monitor from engine.fetch import fetch_all, fetch_poetry, load_cache from engine.pipeline import ( Pipeline, PipelineConfig, get_preset, ) from engine.pipeline.adapters import ( RenderStage, create_items_stage, create_stage_from_display, create_stage_from_effect, ) print(" \033[1;38;5;46mPIPELINE MODE\033[0m") print(" \033[38;5;245mUsing unified pipeline architecture\033[0m") effects_plugins.discover_plugins() monitor = PerformanceMonitor() set_monitor(monitor) preset = get_preset(preset_name) if not preset: print(f" \033[38;5;196mUnknown preset: {preset_name}\033[0m") sys.exit(1) print(f" \033[38;5;245mPreset: {preset.name} - {preset.description}\033[0m") params = preset.to_params() params.viewport_width = 80 params.viewport_height = 24 pipeline = Pipeline( config=PipelineConfig( source=preset.source, display=preset.display, camera=preset.camera, effects=preset.effects, ) ) print(" \033[38;5;245mFetching content...\033[0m") cached = load_cache() if cached: items = cached elif preset.source == "poetry": items, _, _ = fetch_poetry() else: items, _, _ = fetch_all() if not items: print(" \033[38;5;196mNo content available\033[0m") sys.exit(1) print(f" \033[38;5;82mLoaded {len(items)} items\033[0m") display = DisplayRegistry.create(preset.display) if not display: print(f" \033[38;5;196mFailed to create display: {preset.display}\033[0m") sys.exit(1) display.init(80, 24) effect_registry = get_registry() pipeline.add_stage("source", create_items_stage(items, preset.source)) pipeline.add_stage( "render", RenderStage( items, width=80, height=24, camera_speed=params.camera_speed, camera_mode=preset.camera, firehose_enabled=params.firehose_enabled, ), ) for effect_name in preset.effects: effect = effect_registry.get(effect_name) if effect: pipeline.add_stage( f"effect_{effect_name}", create_stage_from_effect(effect, effect_name) ) pipeline.add_stage("display", create_stage_from_display(display, preset.display)) pipeline.build() if not pipeline.initialize(): print(" \033[38;5;196mFailed to initialize pipeline\033[0m") sys.exit(1) print(" \033[38;5;82mStarting pipeline...\033[0m") print(" \033[38;5;245mPress Ctrl+C to exit\033[0m\n") ctx = pipeline.context ctx.params = params ctx.set("display", display) ctx.set("items", items) ctx.set("pipeline", pipeline) try: frame = 0 while True: params.frame_number = frame ctx.params = params result = pipeline.execute(items) if result.success: display.show(result.data) if hasattr(display, "is_quit_requested") and display.is_quit_requested(): if hasattr(display, "clear_quit_request"): display.clear_quit_request() raise KeyboardInterrupt() time.sleep(1 / 60) frame += 1 except KeyboardInterrupt: pass finally: pipeline.cleanup() display.cleanup() print("\n \033[38;5;245mPipeline stopped\033[0m")