hitvid is a high-performance, feature-rich video player designed to run directly in your terminal. It leverages the power of ffmpeg for video processing and chafa for superior character-based rendering to deliver a smooth playback experience. It features a sophisticated concurrency model for pre-rendering frames, ensuring that playback is not hindered by on-the-fly processing.
We've completely rewritten hitvid in Go, achieving a performance leap! We've leveraged multiple technologies to accelerate video rendering. We now support Linux, macOS, Windows, and other platforms!
However, we still keep the previous shell version in the /old directory, leaving it as a last resort for platforms that are really incompatible (although there are almost no such platforms).
- High-Performance Playback: Utilizes a multi-threaded pipeline to decode and render frames in parallel, ahead of playback.
- Automatic Playlist Generation: Automatically detects and queues all supported video files (
.mp4,.mkv,.mov, etc.) in the target video's directory. - Rich Playback Controls: Offers intuitive keyboard shortcuts for pausing, seeking, speed adjustment, and navigating the playlist.
- Highly Customizable Rendering: Provides command-line options to control FPS, character sets (
symbols), color modes, dithering algorithms, and output dimensions. - Efficient Synchronization: Employs condition variables (
sync.Cond) to eliminate busy-waiting, ensuring minimal CPU usage while buffering or paused. - Graceful Cancellation: Uses
context.Contextthroughout the application for clean and immediate shutdown of all processes and goroutines. - Terminal UI: Manages the terminal state, hiding the cursor and using an alternate screen buffer for a clean viewing experience that restores the terminal on exit.
Before using hitvid, you must have the following command-line tools installed and accessible in your system's PATH:
- FFmpeg: The core engine for decoding and extracting frames from video files.
- APT or YUM Installation
sudo apt install ffmpegorsudo yum install ffmpeg - Website & Installation: ffmpeg.org
- APT or YUM Installation
- Chafa: The powerful utility for converting images into high-quality terminal character art.
- Website & Installation: hpjansson.org/chafa
- APT or YUM Installation
sudo apt install chafaorsudo yum install chafa
First, ensure you have installed Go (version 1.18+), ffmpeg, and chafa.
Clone the repository to your local machine (note: example URL).
git clone https://github.com/hitmux/hitvid.git
cd hitvidYou can run the program directly using go run. This is useful for quick plays without needing to build a binary.
# Basic usage
go run hitvid.go /path/to/your/video.mp4
# Advanced usage with custom rendering options
go run hitvid.go -fps 24 -colors full -symbols block /path/to/your/video.webmFor a permanent and faster-launching command, build the executable.
go build -o hitvid hitvid.goThen you can run the compiled binary from anywhere:
./hitvid -w 120 -h 40 /path/to/another/video.mkvThe player's behavior can be fine-tuned with the following flags:
| Flag | Description | Default Value |
|---|---|---|
-video <path> |
Path to the video file. Can also be given as the last positional argument. | (None) |
-fps <integer> |
Frame rate for video extraction and playback. | 15 |
-symbols <str> |
Symbol set for chafa (e.g., block, ascii, legacy, braille). |
block |
-colors <str> |
Color mode for chafa (e.g., none, 16, 256, full). |
256 |
-dither <str> |
Dithering algorithm for chafa (e.g., none, ordered, diffusion). |
ordered |
-w <integer> |
Render width in terminal columns. | Terminal width |
-h <integer> |
Render height in terminal rows. | Terminal height - 1 |
-threads <int> |
Number of parallel threads to use for rendering frames with Chafa. | 4 |
-help |
Display a detailed help message and exit. | false |
Control playback with these keyboard shortcuts:
| Key(s) | Action |
|---|---|
Q / Ctrl+C |
Exit the program immediately. |
Spacebar |
Pause or resume playback. |
+ (Plus) |
Increase playback speed. |
- (Minus) |
Decrease playback speed. |
→ (Right Arrow) |
Seek forward 5 seconds. |
← (Left Arrow) |
Seek backward 5 seconds. |
↓ (Down Arrow) |
Play the next video in the directory playlist. |
↑ (Up Arrow) |
Play the previous video in the directory playlist. |
hitvid is not a simple script; it's a concurrent application designed for efficiency. Its architecture can be broken down into several key areas:
The core of the player is a multi-stage pipeline that processes video frames asynchronously.
-
Frame Extractor (
ffmpeg): Anffmpegprocess is spawned to read the video file, decode it, and save individual frames as JPG images into a temporary directory. This is the first producer. -
Job Dispatcher (Goroutine): A dedicated goroutine runs in the background, watching the temporary directory for new frame images. As soon as a frame appears, it creates a
renderJoband pushes it into a buffered channel (jobs). This decouples frame extraction from frame rendering. -
Frame Renderers (
chafaWorkers): A pool of consumer goroutines (the number is set by-threads) concurrently pullsrenderJobs from thejobschannel. Each worker invokeschafaon its assigned frame image to convert it into a pre-rendered string of terminal characters. The result is stored in therenderedFramesslice at the correct index. -
Playback Loop (Main Goroutine): The final consumer is the main playback loop. It attempts to play
currentFrameIndex. If the frame isn't rendered yet (renderedFrames[currentFrameIndex]isnil), it waits efficiently instead of spinning the CPU.
Managing the state between these concurrent parts is critical.
-
sync.Mutex (stateMutex): A global mutex protects shared state variables likeisPaused,currentFrameIndex,totalFrames,lastRenderedFrame, and user input actions. This prevents race conditions when, for example, the user pauses playback at the same time a new frame is rendered. -
sync.Cond (frameReadyCond): This is the key to efficient waiting. The playback loop usesframeReadyCond.Wait()when it needs a frame that hasn't been rendered yet. This puts the goroutine to sleep, consuming no CPU. When a rendering worker finishes a frame, it callsframeReadyCond.Broadcast(), which wakes up the playback loop to re-check if its required frame is now available. This is vastly more efficient than atime.Sleep()loop.
-
context.Context: Acontext.WithCancelis created for each video played. Thiscontextis passed down to every goroutine andexec.Commandrelated to that video. When the user quits, skips to the next video, or the video finishes,cancel()is called. This sends a cancellation signal down the entire chain, gracefully terminatingffmpeg, any runningchafaprocesses, and all associated goroutines, ensuring no orphaned processes are left behind. -
Main Control Loop: The
main()function contains the top-level control loop. It manages the playlist, handles transitions between videos (next,prev), and re-initializes the state for each new video. This outer loop is responsible for the application's overall lifecycle, while theplayVideofunction manages the lifecycle of a single video playback session.
This project is licensed under the AGPLv3 License.
- This program would not be possible without the incredible work of the FFmpeg team.
- The beautiful terminal output is thanks to the Chafa library by Hans-Peter Jansson.