Skip to content

Instantly share code, notes, and snippets.

@phecdaDia
Created April 12, 2023 00:17
Show Gist options
  • Save phecdaDia/9493ac52071d3f1a049fb1cce586fe28 to your computer and use it in GitHub Desktop.
Save phecdaDia/9493ac52071d3f1a049fb1cce586fe28 to your computer and use it in GitHub Desktop.
Command line video viewer
[package]
name = "cmd-movie"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
strip = 'none'
# Enable high optimizations for dependencies, but not for our code:
[profile.dev.package."*"]
opt-level = 3
# For production, use high optimization
[profile.release]
opt-level = 'z' # Optimize for size
lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
#panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary*
# Link standard library statically
# This is useful for producing a binary that doesn't depend on the presence of
# a C runtime on the target system.
# For more information see https://doc.rust-lang.org/cargo/reference/features.html#feature-unstable
# Features. See https://doc.rust-lang.org/cargo/reference/features.html
[features]
# Example of features that are enabled by default
default = []
# Build to an exe
[[bin]]
name = "cmd-movie"
path = "src/main.rs"
[dependencies]
clap = "4.2.1"
ffmpeg-next = "6.0.0"
/*
Requirements:
- ffmpeg-next ^= 6.0.0
- clap ^= 4.2.1
*/
use std::io::Write;
use clap::{value_parser, Arg, Command};
extern crate ffmpeg_next as ffmpeg;
fn main() {
// Don't format this with rustfmt
#[rustfmt::skip]
let matches = Command::new("Command-line movie viewer")
.version("1.0")
.author("Phecda <hello@phecda.eu>")
.arg(
Arg::new("width")
.long("width")
.value_name("WIDTH")
.help("Sets the width of the window")
.value_parser(value_parser!(u32))
.required(false)
.default_value("300")
)
.arg(
Arg::new("height")
.long("height")
.value_name("HEIGHT")
.help("Sets the height of the window")
.value_parser(value_parser!(u32))
.required(false)
.default_value("70")
)
.arg(
Arg::new("file")
.help("Sets the file to play")
.value_parser(value_parser!(std::path::PathBuf))
.required(true)
).get_matches();
let width = matches.get_one::<u32>("width").unwrap();
let height = matches.get_one::<u32>("height").unwrap();
let file = matches.get_one::<std::path::PathBuf>("file").unwrap();
// Get video stream
let mut format_context = ffmpeg::format::input(&file).unwrap();
let video_stream = format_context.streams().best(ffmpeg::media::Type::Video).ok_or("No video stream found").unwrap();
let video_stream_index = video_stream.index();
let context_decoder = ffmpeg::codec::Context::from_parameters(video_stream.parameters()).unwrap();
let mut decoder = context_decoder.decoder().video().unwrap();
#[rustfmt::skip]
let mut scaler = ffmpeg::software::scaling::context::Context::get(
decoder.format(),
decoder.width(),
decoder.height(),
ffmpeg::format::Pixel::RGB24,
*width,
*height,
ffmpeg::software::scaling::flag::Flags::BILINEAR
).unwrap();
for (stream, packet) in format_context.packets() {
if stream.index() != video_stream_index {
continue;
}
decoder.send_packet(&packet).unwrap();
receive_and_processes_decoded_frames(&mut decoder, &mut scaler).unwrap();
}
}
fn receive_and_processes_decoded_frames(decoder: &mut ffmpeg::decoder::Video, scaler: &mut ffmpeg::software::scaling::context::Context) -> Result<(), ffmpeg::Error> {
let mut decoded = ffmpeg::util::frame::video::Video::empty();
// get stdout
let stdout = std::io::stdout();
let mut handle = stdout.lock();
while decoder.receive_frame(&mut decoded).is_ok() {
let mut rgb_frame = ffmpeg::util::frame::video::Video::empty();
scaler.run(&decoded, &mut rgb_frame).unwrap();
let width = rgb_frame.width();
let height = rgb_frame.height();
let frame_data = rgb_frame.data(0);
//Go through each pixel
for y in 0..height {
for x in 0..width {
let offset = (y * width + x) as usize * 3;
// Get rgb values
let r = frame_data[offset];
let g = frame_data[offset + 1];
let b = frame_data[offset + 2];
write!(handle, "\x1b[38;2;{};{};{}m█", r, g, b).unwrap();
}
write!(handle, "\n").unwrap();
}
// Flush stdout
handle.flush().unwrap();
print!("\x1b[{}A", height);
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment