A high-performance minimal (~1k lines of code) terminal-based multimedia library that renders images, GIFs, and videos using SIXEL graphics with synchronized audio playback.
# Ubuntu/Debian sudo apt update sudo apt install libopencv-dev libsixel-dev ffmpeg cmake build-essential # Arch Linux sudo pacman -S opencv sixel cpr ffmpeg cmake base-devel # macOS (Homebrew) brew install opencv libsixel cpr ffmpeg cmake
Note
For Ubuntu/Debian users: cpr
is not available as a package in Ubuntu/Debian repositories.
You must clone and build it manually.
git clone https://github.com/libcpr/cpr.git cd cpr mkdir build && cd build cmake .. make sudo make install
Alternatively, clone cpr
into your project root and include it as a subdirectory in your CMakeLists.txt
git clone https://github.com/Sarthak2143/sakura.git cd sakura mkdir build && cd build cmake .. make -j$(nproc)
install module via flakes
{ inputs.sakura.url = "github:sarthak2143/sakura"; inputs.sakura.inputs.nixpkgs.follows = "nixpkgs"; outputs = { self, nixpkgs, sakura }: { # change `yourhostname` to your actual hostname nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem { # customize to your system system = "x86_64-linux"; modules = [ ./configuration.nix sakura.nixosModules.sakura ]; }; }; } # enable it in your configuration programs.sakura.enable = true;
install home-manager modules via flakes
{ inputs.sakura.url = "github:sarthak2143/sakura"; inputs.sakura.inputs.nixpkgs.follows = "nixpkgs"; outputs = { self, nixpkgs, home-manager, sakura }: { homeConfigurations."username" = home-manager.lib.homeManagerConfiguration { # ... modules = [ sakura.homeModules.sakura # ... ]; }; }; } # enable it in home manager config programs.sakura.enable = true;
you can run sakura ad-hoc without installing it.
nix run github:sarthak2143/sakura
you can also install it into NixOS modules
{ inputs.sakura.url = "github:sarthak2143/sakura"; inputs.sakura.inputs.nixpkgs.follows = "nixpkgs"; outputs = { self, nixpkgs, sakura }: { # change `yourhostname` to your actual hostname nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem { # customize to your system system = "x86_64-linux"; modules = [ ./configuration.nix { environment.systemPackages = [ sakura.packages.${system}.default ]; } ]; }; }; }
When you run ./sakura
, you'll see an interactive menu:
Sakura Video Player with SIXEL
1. Image
2. GIF
3. Video (URL)
4. Video (File)
Choose option (1-4):
# Local image ./sakura > 1 > /path/to/image.jpg # Online image ./sakura > 1 > https://example.com/image.png
# Local GIF ./sakura > 2 > /path/to/animation.gif # Online GIF ./sakura > 2 > https://example.com/animation.gif
./sakura > 3 > https://example.com/video.mp4
./sakura > 4 > /path/to/video.mp4
Test with these sample commands:
# Test image rendering echo -e "1\nhttps://picsum.photos/800/600" | ./sakura # Test GIF animation echo -e "2\nhttps://media.giphy.com/media/3o7qE1YN7aBOFPRw8E/giphy.gif" | ./sakura # Test local video echo -e "4\nmedia/your_video.mp4" | ./sakura
fastResize=true
, otherwise INTER_AREA)
// Core rendering pipeline cv::Mat rgb_img; cv::cvtColor(img, rgb_img, cv::COLOR_BGR2RGB); sixel_dither_t *dither = sixel_dither_new(palette_size); sixel_encode(rgb_img.data, width, height, 3, dither, output);Audio-Video Synchronization
The player implements sophisticated timing control:
// High-precision frame timing auto frame_duration_ns = std::chrono::nanoseconds(static_cast<long long>(1000000000.0 / fps)); auto target_frame_time = start_time + (frame_duration_ns * frame_number); // Smart frame skipping if (frame_number < target_frame_number) { int frames_behind = target_frame_number - frame_number; if (frames_behind > 2) { // Skip frames to catch up } }Video Processing Architecture
// Video processing flow // Reader thread (pre-decode + pre-scale into a bounded queue) // Main thread: encode to SIXEL and pace using steady_clockPerformance Optimizations Frame Timing Optimizations
std::chrono::steady_clock
with sleep-until pacing// Adaptive scaling based on FPS // Fit modes: // - CONTAIN: keep aspect within terminal bounds (no crop) // - COVER: fill entire terminal (may crop) // - STRETCH: fill width and height (distorts aspect) // Fast pre-scaling: // options.fastResize = true; // use INTER_NEAREST for maximum FPS
fastResize=true
), INTER_AREA for qualityxterm -ti vt340
mlterm Native Default wezterm Configurable Enable in config foot Native Default mintty Optional --enable-sixel
iTerm2 Beta Enable in preferences
# Launch with SIXEL support xterm -ti vt340 -geometry 120x40 # Or add to ~/.Xresources xterm*decTerminalID: vt340
-- ~/.config/wezterm/wezterm.lua return { enable_sixel = true, max_fps = 60, }
Main rendering engine with the following public methods:
class Sakura { public: // Image rendering bool renderFromUrl(const std::string &url, const RenderOptions &options); bool renderFromMat(const cv::Mat &img, const RenderOptions &options); // Video/GIF rendering bool renderVideoFromFile(const std::string &path, const RenderOptions &options); bool renderVideoFromUrl(const std::string &url, const RenderOptions &options); bool renderGifFromUrl(const std::string &url, const RenderOptions &options); // SIXEL rendering std::string renderSixel(const cv::Mat &img, int paletteSize = 256); };
struct RenderOptions { int width = 0; int height = 0; int paletteSize = 256; CharStyle style = SIMPLE; RenderMode mode = EXACT; DitherMode dither = NONE; bool aspectRatio = true; double contrast = 1.2; double brightness = 0.0; double terminalAspectRatio = 1.0; int queueSize = 16; // size of predecode queue int prebufferFrames = 4; // frames to prebuffer before audio bool staticPalette = false; // reuse first palette for all frames FitMode fit = COVER; // STRETCH, COVER, CONTAIN bool fastResize = false; // use INTER_NEAREST when true // Throughput controls double targetFps = 0.0; // 0 = follow source FPS; otherwise downsample to this bool adaptivePalette = false; int minPaletteSize = 64; int maxPaletteSize = 256; bool adaptiveScale = false; // dynamically adjust scale based on drop rate double minScaleFactor = 0.80; double maxScaleFactor = 1.00; double scaleStep = 0.05; };
enum RenderMode { EXACT, ASCII_COLOR, ASCII_GRAY, SIXEL }; enum FitMode { STRETCH, COVER, CONTAIN };
#include "sakura.hpp" int main() { Sakura renderer; RenderOptions options; options.mode = SIXEL; options.paletteSize = 256; options.width = 800; options.height = 600; options.queueSize = 48; options.prebufferFrames = 12; options.staticPalette = true; options.fit = COVER; options.fastResize = true; // maximize FPS options.targetFps = 30.0; // stabilize rendering on SIXEL terminals options.adaptivePalette = true; options.minPaletteSize = 64; options.maxPaletteSize = 256; options.adaptiveScale = true; // auto-tune size when drops persist options.minScaleFactor = 0.85; options.maxScaleFactor = 1.00; options.scaleStep = 0.05; // Render image renderer.renderFromUrl("https://example.com/image.jpg", options); // Render video with audio renderer.renderVideoFromFile("video.mp4", options); return 0; }
// Load and process image cv::Mat image = cv::imread("input.jpg"); cv::GaussianBlur(image, image, cv::Size(5, 5), 0); // Render with custom options RenderOptions opts; opts.contrast = 1.2; opts.brightness = 10; renderer.renderFromMat(image, opts);
// Process multiple images std::vector<std::string> urls = { "https://example.com/img1.jpg", "https://example.com/img2.jpg", "https://example.com/img3.jpg" }; for (const auto& url : urls) { renderer.renderFromUrl(url, options); std::this_thread::sleep_for(std::chrono::seconds(2)); }
# Check file exists and permissions ls -la /path/to/video.mp4 # Verify OpenCV codec support ffmpeg -codecs | grep h264 # Try different format ffmpeg -i input.mov -c:v libx264 -c:a aac output.mp4
# Test terminal SIXEL support echo -e '\ePq"1;1;100;100#0;2;0;0;0#1;2;100;100;0#1~~@@vv@@~~@@~~$#0~~@@~~@@~~@@vv$#1!14~\e\\' # Launch with SIXEL-capable terminal xterm -ti vt340 -e ./sakura
# Check ffplay installation which ffplay # Test audio playback separately ffplay -nodisp -autoexit video.mp4 # Check audio permissions (containers) pulseaudio --check
# Reduce video resolution ffmpeg -i input.mp4 -vf scale=640:480 output.mp4 # Lower frame rate ffmpeg -i input.mp4 -r 15 output.mp4 # Use hardware acceleration if available ffmpeg -hwaccel auto -i input.mp4 output.mp4
// Enable debug output #define SAKURA_DEBUG 1 // Check rendering statistics std::cout << "Frames rendered: " << frames_rendered << std::endl; std::cout << "Frames dropped: " << frames_dropped << std::endl; std::cout << "Drop rate: " << (frames_dropped * 100.0 / total_frames) << "%" << std::endl;
Monitor performance metrics during playback:
# Terminal output shows real-time stats Frame: 450/1800 Dropped: 23 Audio: ON # Calculate performance Drop Rate: 23/450 = 5.1% # Acceptable: <10% Render Rate: 94.9% # Good: >90%
git clone https://github.com/Sarthak2143/sakura.git cd sakura # Install development dependencies sudo apt install clang-format cppcheck valgrind # Build with debug symbols cmake -DCMAKE_BUILD_TYPE=Debug .. make -j$(nproc)
# Run basic functionality tests ./test_suite.sh # Memory leak detection valgrind --leak-check=full ./sakura # Performance profiling perf record ./sakura perf report
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4