add ascii mode
This commit is contained in:
1
termshrek/.gitignore
vendored
Normal file
1
termshrek/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target
|
||||
448
termshrek/Cargo.lock
generated
Normal file
448
termshrek/Cargo.lock
generated
Normal file
@@ -0,0 +1,448 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-next"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d658424d233cbd993a972dd73a66ca733acd12a494c68995c9ac32ae1fe65b40"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "8.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bca20aa4ee774fe384c2490096c122b0b23cf524a9910add0686691003d797b"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.182"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termshrek"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"ffmpeg-next",
|
||||
"term_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
9
termshrek/Cargo.toml
Normal file
9
termshrek/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "termshrek"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.60", features = ["derive"] }
|
||||
ffmpeg-next = "8.0.0"
|
||||
term_size = "0.3.2"
|
||||
284
termshrek/src/main.rs
Normal file
284
termshrek/src/main.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
// TODO CLI tool, read resolution of term
|
||||
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use ffmpeg::software::scaling::{context::Context as ScalingContext, flag::Flags};
|
||||
use ffmpeg::util::format::pixel::Pixel;
|
||||
use ffmpeg_next as ffmpeg;
|
||||
|
||||
// const ASCII_GRADIENT: &[char] = &[' ', '░', '▒', '▓', '█'];
|
||||
const ASCII_GRADIENT: &[char] = &[' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
enum ArtStyle {
|
||||
Pixel,
|
||||
Ascii,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
keep_ar: bool,
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
loop_video: bool,
|
||||
#[arg(short, long, value_enum, default_value_t =ArtStyle::Pixel)]
|
||||
art_style: ArtStyle,
|
||||
}
|
||||
|
||||
struct VideoReader {
|
||||
input: ffmpeg::format::context::Input,
|
||||
fps: f32,
|
||||
aspect_ratio: f32,
|
||||
dims: (u32, u32),
|
||||
decoder: ffmpeg::decoder::Video,
|
||||
scaler: ScalingContext,
|
||||
stream_index: usize,
|
||||
frame: ffmpeg::util::frame::Video,
|
||||
rgb_frame: ffmpeg::util::frame::Video,
|
||||
loop_video: bool,
|
||||
}
|
||||
|
||||
impl VideoReader {
|
||||
fn new(path: &Path, loop_video: bool, term_width: usize, term_height: usize) -> Self {
|
||||
ffmpeg::init().unwrap();
|
||||
|
||||
let input = ffmpeg::format::input(path).unwrap();
|
||||
|
||||
let stream = input
|
||||
.streams()
|
||||
.best(ffmpeg::media::Type::Video)
|
||||
.expect("video stream not found");
|
||||
|
||||
let framerate = stream.avg_frame_rate();
|
||||
let framerate_f32 = framerate.numerator() as f32 / framerate.denominator() as f32;
|
||||
|
||||
let stream_index = stream.index();
|
||||
|
||||
let codec_context =
|
||||
ffmpeg::codec::context::Context::from_parameters(stream.parameters()).unwrap();
|
||||
|
||||
let decoder = codec_context.decoder().video().unwrap();
|
||||
|
||||
// Find aspect ratio
|
||||
let width = decoder.width();
|
||||
let height = decoder.height();
|
||||
|
||||
let sar = decoder.aspect_ratio();
|
||||
|
||||
let sar_f32 = if sar.denominator() != 0 {
|
||||
sar.numerator() as f32 / sar.denominator() as f32
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let dar = (width as f32 / height as f32) * sar_f32;
|
||||
|
||||
// Find target resolution
|
||||
let target_width;
|
||||
let target_height;
|
||||
|
||||
let prep_term_height = (term_height - 2) * 2;
|
||||
|
||||
if dar > term_width as f32 / (prep_term_height) as f32 {
|
||||
target_height = (term_width as f32 / dar).floor() as u32;
|
||||
target_width = term_width as u32;
|
||||
} else {
|
||||
target_height = (prep_term_height) as u32;
|
||||
target_width = ((prep_term_height) as f32 * dar).floor() as u32;
|
||||
}
|
||||
|
||||
let scaler = ScalingContext::get(
|
||||
decoder.format(),
|
||||
width,
|
||||
height,
|
||||
Pixel::RGB24,
|
||||
target_width,
|
||||
target_height.div_euclid(2) * 2,
|
||||
Flags::BILINEAR,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
input,
|
||||
fps: framerate_f32,
|
||||
aspect_ratio: dar,
|
||||
dims: (target_width, target_height.div_euclid(2) * 2),
|
||||
decoder,
|
||||
scaler,
|
||||
stream_index,
|
||||
frame: ffmpeg::util::frame::Video::empty(),
|
||||
rgb_frame: ffmpeg::util::frame::Video::empty(),
|
||||
loop_video,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_frame_data(&mut self) -> Vec<[u8; 2]> {
|
||||
self.scaler.run(&self.frame, &mut self.rgb_frame).unwrap();
|
||||
|
||||
let width = self.rgb_frame.width() as usize;
|
||||
let height = self.rgb_frame.height() as usize;
|
||||
let stride = self.rgb_frame.stride(0);
|
||||
|
||||
let mut rgb: Vec<[u8; 2]> = Vec::with_capacity(width * height * 3);
|
||||
|
||||
let mut y = 0;
|
||||
while y < height {
|
||||
let upper = y * stride;
|
||||
let lower = (y + 1) * stride;
|
||||
let row_upper = &self.rgb_frame.data(0)[upper..upper + width * 3];
|
||||
let row_lower = &self.rgb_frame.data(0)[lower..lower + width * 3];
|
||||
let dualrow: Vec<[u8; 2]> = row_upper
|
||||
.iter()
|
||||
.zip(row_lower.iter())
|
||||
.map(|(&x, &y)| [x, y])
|
||||
.collect();
|
||||
rgb.extend(dualrow);
|
||||
y += 2
|
||||
}
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
fn next_frame(&mut self) -> Option<Vec<[u8; 2]>> {
|
||||
let mut packets_iter = self.input.packets();
|
||||
loop {
|
||||
if let Some((stream, packet)) = packets_iter.next() {
|
||||
if stream.index() != self.stream_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.decoder.send_packet(&packet).unwrap();
|
||||
|
||||
if let Ok(_) = self.decoder.receive_frame(&mut self.frame) {
|
||||
return Some(self.extract_frame_data());
|
||||
}
|
||||
} else {
|
||||
self.decoder.flush();
|
||||
self.input.seek(0, ..).unwrap();
|
||||
packets_iter = self.input.packets();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_pixel_art(frame: &Vec<[u8; 2]>, reader: &VideoReader, buf: &mut Vec<u8>) {
|
||||
for row in 0..reader.dims.1 / 2 {
|
||||
for col in 0..reader.dims.0 {
|
||||
let idx = 3 * (row * reader.dims.0 + col);
|
||||
let reds = frame.get(idx as usize).unwrap_or_else(|| {
|
||||
panic!("Index: {idx} is out of bounds at row {row} and col {col}. ")
|
||||
});
|
||||
let greens = frame.get((idx + 1) as usize).unwrap_or_else(|| {
|
||||
panic!("Index: {idx} is out of bounds at row {row} and col {col}. ")
|
||||
});
|
||||
|
||||
let blues = frame.get((idx + 2) as usize).unwrap_or_else(|| {
|
||||
panic!("Index: {idx} is out of bounds at row {row} and col {col}. ")
|
||||
});
|
||||
|
||||
buf.extend_from_slice(
|
||||
format!(
|
||||
"\x1b[48;2;{br};{bg};{bb}m\x1b[38;2;{tr};{tg};{tb}m▀",
|
||||
br = reds[1],
|
||||
bg = greens[1],
|
||||
bb = blues[1],
|
||||
tr = reds[0],
|
||||
tg = greens[0],
|
||||
tb = blues[0],
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
}
|
||||
buf.extend_from_slice(b"\x1b[0m\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn luminance(r: u8, g: u8, b: u8) -> f32 {
|
||||
let res = 0.2126 * f32::try_from(r).unwrap_or_else(|e| unreachable!("{e}"))
|
||||
+ 0.7152 * f32::try_from(g).unwrap_or_else(|e| unreachable!("{e}"))
|
||||
+ 0.0722 * f32::try_from(b).unwrap_or_else(|e| unreachable!("{e}"));
|
||||
res
|
||||
}
|
||||
|
||||
fn render_ascii_art(frame: &Vec<[u8; 2]>, reader: &VideoReader, buf: &mut Vec<u8>) {
|
||||
for row in 0..reader.dims.1 / 2 {
|
||||
for col in 0..reader.dims.0 {
|
||||
let idx = 3 * (row * reader.dims.0 + col);
|
||||
let reds = frame.get(idx as usize).unwrap_or_else(|| {
|
||||
panic!("Index: {idx} is out of bounds at row {row} and col {col}. ")
|
||||
});
|
||||
let greens = frame.get((idx + 1) as usize).unwrap();
|
||||
let blues = frame.get((idx + 2) as usize).unwrap();
|
||||
|
||||
// color
|
||||
// buf.extend_from_slice(
|
||||
// format!(
|
||||
// "\x1b[38;2;{tr};{tg};{tb}m",
|
||||
// tr = reds[0],
|
||||
// tg = greens[0],
|
||||
// tb = blues[0],
|
||||
// )
|
||||
// .as_bytes(),
|
||||
// );
|
||||
let mut extend = [0u8; 4];
|
||||
buf.extend_from_slice(
|
||||
ASCII_GRADIENT[(luminance(reds[0], greens[0], blues[0])) as usize
|
||||
* (ASCII_GRADIENT.len() - 1)
|
||||
/ 255]
|
||||
.encode_utf8(&mut extend)
|
||||
.as_bytes(),
|
||||
);
|
||||
}
|
||||
buf.extend_from_slice(b"\x1b[0m\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let term_dims =
|
||||
term_size::dimensions().unwrap_or_else(|| panic!("Could not find terminal dimensions"));
|
||||
|
||||
let mut reader = VideoReader::new(
|
||||
Path::new("/home/jan/Videos/rotating_panda.mkv"),
|
||||
args.loop_video,
|
||||
term_dims.0,
|
||||
term_dims.1,
|
||||
);
|
||||
|
||||
let frame_duration_micros: Duration =
|
||||
Duration::from_micros((1_000_000.0 / reader.fps).floor() as u64);
|
||||
|
||||
let mut out = io::stdout().lock();
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let start_time = Instant::now();
|
||||
|
||||
// Clear screen and hide cursor
|
||||
let _ = out.write_all(b"\x1b[2J\x1b[?25l");
|
||||
|
||||
while let Some(frame) = reader.next_frame() {
|
||||
let time = start_time.elapsed();
|
||||
|
||||
buf.extend_from_slice(b"\x1b[J\x1b[H"); // move to start
|
||||
match args.art_style {
|
||||
ArtStyle::Pixel => render_pixel_art(&frame, &reader, &mut buf),
|
||||
ArtStyle::Ascii => render_ascii_art(&frame, &reader, &mut buf),
|
||||
}
|
||||
|
||||
let _ = out.write_all(&buf);
|
||||
let _ = out.flush();
|
||||
|
||||
buf.clear();
|
||||
|
||||
let sleep_time = frame_duration_micros - (start_time.elapsed() - time);
|
||||
if sleep_time.as_micros() > 0 {
|
||||
thread::sleep(sleep_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user