// backgroundDisplay.h // // Decode a video file (MP4 or any FFmpeg-supported container/codec) and play // it in the terminal as truecolor ASCII art, honouring the video's aspect // ratio. // // Terminal behaviour: // - If the terminal can be resized programmatically ("adjustable"), the // current width is kept and the height is set so the ASCII grid matches // the video aspect ratio (terminal cells are roughly 2:1 tall:wide). // - Otherwise the video is letterboxed to the largest size that fits the // current terminal and the video frame size (e.g. "1280x720") is printed // in the top-left corner of the terminal. // // This module is self-contained (raw ANSI escape sequences, no ncurses). // Decoding runs on a background thread; playVideo() blocks the calling // thread for the duration of playback, so callers can run several show // components concurrently by using their own std::thread. #pragma once #include #include #include "timeline.h" namespace bd { struct TermSize { int cols = 0; // character columns int rows = 0; // character rows bool ok = false; // false when stdout is not a tty or the size is unknown }; struct GridSize { int cols = 0; int rows = 0; }; // --- terminal helpers ----------------------------------------------------- // True if stdout is an interactive terminal (isatty). bool isTerminal(); // Current terminal size in character cells. TermSize terminalSize(); // Ask the terminal to resize to (rows x cols) via the xterm sequence // "\x1b[8;rows;colst" and verify it actually took effect (TIOCGWINSZ). bool resizeTerminal(int rows, int cols); // Probe whether the terminal honours programmatic resize requests. Restores // the original size afterwards. bool isTerminalAdjustable(); // Largest whole grid (cols x rows) with the video aspect ratio that fits // inside (maxCols x maxRows). Rows are counted with 2:1 cell aspect. GridSize fitSizeForAspect(int videoW, int videoH, int maxCols, int maxRows); // --- video playback ------------------------------------------------------- struct PlayVideoOptions { std::string path; bool loop = false; // repeat the video until stop float intensity = 1.0f; // brightness multiplier for fg/bg bool tryResize = true; // false => force the "not adjustable" fit path int topRows = 0; // reserve this many rows ABOVE the video (e.g. lyrics) int bottomRows = 0; // reserve this many rows BELOW the video GridSize* outGrid = nullptr; // receives the computed video grid (optional) int* outOffsetX = nullptr; // receives the video's horizontal offset in cols std::atomic* gridReady = nullptr; // set true once the layout is reported std::atomic* stop = nullptr; // set to true to end playback early }; // Play `path` as ASCII art, paced by `timeline` (may be null for an // independent local clock). Blocks until the video ends (or `stop` is set). // Returns: // 0 playback finished (or stopped) cleanly // 1 failed to open/decode the video // 2 stdout is not a tty (nothing was written) int playVideo(const std::string& path, Timeline* timeline, const PlayVideoOptions& opts = {}); } // namespace bd