Initial code commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
274
Cargo.lock
generated
Normal file
274
Cargo.lock
generated
Normal file
@@ -0,0 +1,274 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "artvidnet-linky"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ffmpeg-rs",
|
||||
"getopts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.59.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
|
||||
|
||||
[[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.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-rs"
|
||||
version = "5.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0e17f67b535f0917a6790db6046293c1f15b7800702dd092bdd8271d023d586"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "5.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d780b36e092254367e2f1f21191992735c8e23f31a5a5a8678db3a79f775021f"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[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.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[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"
|
||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "artvidnet-linky"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ffmpeg-rs = "5.2.1"
|
||||
getopts = "0.2.21"
|
||||
432
src/main.rs
Normal file
432
src/main.rs
Normal file
@@ -0,0 +1,432 @@
|
||||
use std::{
|
||||
cmp, env,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||
path::Path,
|
||||
thread::{self},
|
||||
time, fs::File, io::Write,
|
||||
};
|
||||
|
||||
use ffmpeg_rs::{
|
||||
format::{input, Pixel},
|
||||
frame::{Video},
|
||||
media::Type,
|
||||
software::scaling::Flags,
|
||||
Error,
|
||||
};
|
||||
use getopts::Options;
|
||||
|
||||
extern crate getopts;
|
||||
|
||||
struct LinkyFrame {
|
||||
data: [u8; 4 * 204 * 5],
|
||||
}
|
||||
|
||||
impl LinkyFrame {
|
||||
fn new() -> LinkyFrame {
|
||||
LinkyFrame {
|
||||
data: [0; 4 * 204 * 5],
|
||||
}
|
||||
}
|
||||
|
||||
fn cols() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
fn rows() -> usize {
|
||||
204
|
||||
}
|
||||
|
||||
fn universes() -> u16 {
|
||||
5 * 2
|
||||
}
|
||||
|
||||
fn set_pixel(&mut self, index: usize, rgb: (u8, u8, u8), brightness: f32) {
|
||||
let r: f32 = f32::from(rgb.0) * brightness;
|
||||
let g: f32 = f32::from(rgb.1) * brightness;
|
||||
let b: f32 = f32::from(rgb.2) * brightness;
|
||||
let rgb = unsafe {
|
||||
(
|
||||
r.to_int_unchecked(),
|
||||
g.to_int_unchecked(),
|
||||
b.to_int_unchecked(),
|
||||
)
|
||||
};
|
||||
|
||||
let white = cmp::min(cmp::min(rgb.0, rgb.1), rgb.2);
|
||||
self.data[index * 4] = rgb.0 - white;
|
||||
self.data[index * 4 + 1] = rgb.1 - white;
|
||||
self.data[index * 4 + 2] = rgb.2 - white;
|
||||
self.data[index * 4 + 3] = white;
|
||||
}
|
||||
|
||||
fn get_universe_data(&self, uni: u16) -> Vec<u8> {
|
||||
if uni > LinkyFrame::universes() {
|
||||
()
|
||||
}
|
||||
let mut data = Vec::new();
|
||||
|
||||
let start = usize::from(uni / 2) * 204 * 4;
|
||||
let middle = usize::from(uni / 2) * 204 * 4 + 120 * 4;
|
||||
let stop = usize::from(uni / 2 + 1) * 204 * 4;
|
||||
|
||||
let (begin, end) = if uni < 8 {
|
||||
if uni % 2 == 0 {
|
||||
(start, middle)
|
||||
} else {
|
||||
(middle, stop)
|
||||
}
|
||||
} else {
|
||||
if uni % 2 == 0 {
|
||||
(middle, stop)
|
||||
} else {
|
||||
(start, middle)
|
||||
}
|
||||
};
|
||||
|
||||
for i in 0..((end - begin) / 48) {
|
||||
// Elation RGBW led light structure, each 12 lights (a single strip) begins with this sequence.
|
||||
data.extend_from_slice(&[0, 255, 0]);
|
||||
// Then fill RGBW data.
|
||||
data.extend_from_slice(&self.data[begin + i * 48..begin + (i + 1) * 48]);
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
struct ArtNetDMX {
|
||||
data: Vec<u8>,
|
||||
dmx_length: usize,
|
||||
}
|
||||
|
||||
impl ArtNetDMX {
|
||||
fn new() -> ArtNetDMX {
|
||||
let mut packet: ArtNetDMX = ArtNetDMX {
|
||||
data: Vec::new(),
|
||||
dmx_length: 0,
|
||||
};
|
||||
packet.data.resize(18, 0);
|
||||
|
||||
// ArtNet packet identifier
|
||||
packet.data[0..8].copy_from_slice(b"Art-Net\0");
|
||||
|
||||
// OpCode 0x5000 (OpDmx)
|
||||
packet.data[8] = 0x00;
|
||||
packet.data[9] = 0x50;
|
||||
|
||||
// ProtVer - always has to be 14, high byte first
|
||||
packet.data[10] = 0x00;
|
||||
packet.data[11] = 14;
|
||||
|
||||
// Sequence - should be set after creation
|
||||
packet.data[12] = 0;
|
||||
|
||||
// Physical port - doesn't matter
|
||||
packet.data[13] = 0;
|
||||
|
||||
// Universe - should be set after creation
|
||||
packet.data[14] = 0;
|
||||
packet.data[15] = 0;
|
||||
|
||||
// Data length - should be set after creation
|
||||
packet.data[16] = 0;
|
||||
packet.data[17] = 0;
|
||||
|
||||
packet
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_seq(&mut self, seq: u8) -> u8 {
|
||||
let seq = if seq > 0 { seq } else { 1 };
|
||||
self.data[12] = seq;
|
||||
if seq < 255 {
|
||||
seq + 1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn unset_seq(&mut self) {
|
||||
self.data[12] = 0;
|
||||
}
|
||||
|
||||
fn set_universe(&mut self, uni: u16) {
|
||||
let uni = uni & 0x7fff;
|
||||
self.data[14..16].copy_from_slice(&uni.to_le_bytes());
|
||||
}
|
||||
|
||||
fn set_length(&mut self, length: u16) {
|
||||
let length = cmp::min(512, length);
|
||||
self.dmx_length = usize::from(length);
|
||||
self.data.resize(18 + self.dmx_length, 0);
|
||||
self.data[16..18].copy_from_slice(&length.to_be_bytes());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_values(&mut self, values: &[u8]) {
|
||||
self.set_length(u16::try_from(values.len()).unwrap());
|
||||
self.data[18..(18 + self.dmx_length)].copy_from_slice(values);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_value_full(&mut self, values: &[u8]) {
|
||||
self.set_length(512);
|
||||
self.data[18..18 + values.len()].copy_from_slice(values);
|
||||
self.data[18 + values.len()..530].fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_frames<F: FnMut(&Video, u64), P: AsRef<Path>>(
|
||||
file_path: P,
|
||||
mut frame_callback: F,
|
||||
play_realtime: bool,
|
||||
) -> Result<(), Error> {
|
||||
if let Ok(mut ictx) = input(&file_path) {
|
||||
let video_stream = ictx
|
||||
.streams()
|
||||
.best(Type::Video)
|
||||
.ok_or(ffmpeg_rs::Error::StreamNotFound)?;
|
||||
let video_stream_index = video_stream.index();
|
||||
let video_stream_timebase = video_stream.time_base();
|
||||
|
||||
let context_decoder =
|
||||
ffmpeg_rs::codec::context::Context::from_parameters(video_stream.parameters())?;
|
||||
let mut decoder = context_decoder.decoder().video()?;
|
||||
|
||||
let mut scaler = ffmpeg_rs::software::scaling::Context::get(
|
||||
decoder.format(),
|
||||
decoder.width(),
|
||||
decoder.height(),
|
||||
Pixel::RGB24,
|
||||
decoder.width(),
|
||||
decoder.height(),
|
||||
Flags::BILINEAR,
|
||||
)?;
|
||||
|
||||
let mut frame_index = 0;
|
||||
let mut last_pts = 0;
|
||||
let mut process_and_receive_frames =
|
||||
|decoder: &mut ffmpeg_rs::decoder::Video| -> Result<(), Error> {
|
||||
let mut decoded = Video::empty();
|
||||
let mut rgb_frame = Video::empty();
|
||||
while decoder.receive_frame(&mut decoded).is_ok() {
|
||||
scaler.run(&decoded, &mut rgb_frame)?;
|
||||
frame_callback(&rgb_frame, frame_index);
|
||||
|
||||
let frametime = match decoded.pts() {
|
||||
Some(pts) => {
|
||||
let res = ((pts - last_pts) as f64) * f64::from(video_stream_timebase);
|
||||
last_pts = pts;
|
||||
res
|
||||
}
|
||||
None => 1.0 / 24.0,
|
||||
};
|
||||
if play_realtime {
|
||||
thread::sleep(time::Duration::from_secs_f64(frametime));
|
||||
}
|
||||
frame_index += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (stream, packet) in ictx.packets() {
|
||||
if stream.index() == video_stream_index {
|
||||
decoder.send_packet(&packet)?;
|
||||
process_and_receive_frames(&mut decoder)?;
|
||||
}
|
||||
}
|
||||
decoder.send_eof()?;
|
||||
process_and_receive_frames(&mut decoder)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn blurred_pixel(data: &[(u8, u8, u8)], stride: usize, pix_index: usize, blur_radius: u32) -> (u8, u8, u8) {
|
||||
let blur_size: usize = usize::try_from(blur_radius).unwrap();
|
||||
let box_start: usize = pix_index - blur_size * stride - blur_size;
|
||||
let mut r: u32 = 0;
|
||||
let mut g: u32 = 0;
|
||||
let mut b: u32 = 0;
|
||||
|
||||
for i in 0..blur_size * 2 + 1 {
|
||||
for j in 0..blur_size * 2 + 1 {
|
||||
let pixel: usize = box_start + i * stride + j;
|
||||
r += u32::from(data[pixel].0);
|
||||
g += u32::from(data[pixel].1);
|
||||
b += u32::from(data[pixel].2);
|
||||
}
|
||||
}
|
||||
|
||||
// Include the center pixel/line
|
||||
let blur_radius = blur_radius + 1;
|
||||
r /= blur_radius * blur_radius;
|
||||
g /= blur_radius * blur_radius;
|
||||
b /= blur_radius * blur_radius;
|
||||
|
||||
(
|
||||
u8::try_from(r).unwrap_or(255),
|
||||
u8::try_from(g).unwrap_or(255),
|
||||
u8::try_from(b).unwrap_or(255),
|
||||
)
|
||||
}
|
||||
|
||||
fn gen_linky_frame(frame: &Video, index: u64, blur_size: u32, brightness: f32) -> LinkyFrame {
|
||||
let mut linky_frame = LinkyFrame::new();
|
||||
println!(
|
||||
"Processing frame {}: {} x {}",
|
||||
index,
|
||||
frame.width(),
|
||||
frame.height()
|
||||
);
|
||||
|
||||
let usable_width = frame.width() - 2 * blur_size;
|
||||
let usable_height = frame.height() - 2 * blur_size;
|
||||
|
||||
let base_index = usize::try_from(blur_size * frame.width() + blur_size).unwrap();
|
||||
let row_step = usize::try_from(usable_height - 1).unwrap() / (LinkyFrame::rows() - 1);
|
||||
let col_step = usize::try_from(usable_width - 1).unwrap() / (LinkyFrame::cols() - 1);
|
||||
let pix_stride = frame.stride(0) / 3;
|
||||
|
||||
let video_plane: &[(u8, u8, u8)] = frame.plane(0);
|
||||
|
||||
for i in 0..LinkyFrame::cols() {
|
||||
for j in 0..LinkyFrame::rows() {
|
||||
let pix_index = base_index
|
||||
+ row_step * j * pix_stride
|
||||
+ col_step * i;
|
||||
let rgb = if blur_size > 0 {
|
||||
blurred_pixel(
|
||||
video_plane,
|
||||
pix_stride,
|
||||
pix_index,
|
||||
blur_size,
|
||||
)
|
||||
} else {
|
||||
video_plane[pix_index]
|
||||
};
|
||||
|
||||
linky_frame.set_pixel(i * LinkyFrame::rows() + j, rgb, brightness);
|
||||
}
|
||||
}
|
||||
linky_frame
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut options = Options::new();
|
||||
options.optopt("f", "file", "Video file to parse", "FILE");
|
||||
options.optopt(
|
||||
"s",
|
||||
"send",
|
||||
"Send the resulting ArtNet packets to this address",
|
||||
"ADDRESS:PORT",
|
||||
);
|
||||
options.optopt(
|
||||
"w",
|
||||
"write",
|
||||
"Write the ArtNet packets as a single byte stream to this file",
|
||||
"FILE",
|
||||
);
|
||||
options.optopt(
|
||||
"r",
|
||||
"radius",
|
||||
"Blurring radius to use when processing frames - 0 means no blur",
|
||||
"NUMBER",
|
||||
);
|
||||
options.optopt(
|
||||
"b",
|
||||
"brightness",
|
||||
"Normalized float scaling the brightness of each pixel (0.0-1.0 clamped)",
|
||||
"FLOAT",
|
||||
);
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let program = args[0].clone();
|
||||
|
||||
let matches = match options.parse(args) {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
print_usage(program, options);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !matches.opt_present("f") {
|
||||
print_usage(program, options);
|
||||
return;
|
||||
}
|
||||
if !matches.opt_present("s") && !matches.opt_present("w") {
|
||||
return;
|
||||
}
|
||||
|
||||
let blur_size = matches.opt_get::<u32>("r").unwrap_or_default().unwrap_or(0);
|
||||
let mut brightness = matches
|
||||
.opt_get::<f32>("b")
|
||||
.unwrap_or_default()
|
||||
.unwrap_or(1.0);
|
||||
brightness = brightness.clamp(0.0, 1.0);
|
||||
|
||||
if matches.opt_present("s") {
|
||||
let remote = matches.opt_str("s").unwrap();
|
||||
|
||||
let socket = match UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)) {
|
||||
Ok(sock) => sock,
|
||||
Err(e) => panic!("{}", e),
|
||||
};
|
||||
|
||||
// let mut seq = 0;
|
||||
let frame_callback = |frame: &Video, index: u64| {
|
||||
let linky_frame = gen_linky_frame(frame, index, blur_size, brightness);
|
||||
for u in 0..LinkyFrame::universes() {
|
||||
let mut packet = ArtNetDMX::new();
|
||||
packet.set_universe(u);
|
||||
packet.unset_seq();
|
||||
// seq = packet.set_seq(seq);
|
||||
// println!("Sequence: {}", seq);
|
||||
|
||||
let linky_data = linky_frame.get_universe_data(u);
|
||||
// println!("Universe {} size: {}", u, linky_data.len());
|
||||
// packet.set_values(&linky_data);
|
||||
packet.set_value_full(&linky_data);
|
||||
|
||||
// for i in 0..packet.data.len() {
|
||||
// print!("{:02X} ", packet.data[i]);
|
||||
// if i % 24 == 23 {
|
||||
// print!("\n");
|
||||
// }
|
||||
// }
|
||||
// print!("\n");
|
||||
|
||||
socket.send_to(&packet.data, remote.clone()).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
process_frames(&matches.opt_str("f").unwrap(), frame_callback, true).unwrap();
|
||||
} else if matches.opt_present("w") {
|
||||
let mut file = File::create(matches.opt_str("w").unwrap()).unwrap();
|
||||
let frame_callback = |frame: &Video, index: u64| {
|
||||
let linky_frame = gen_linky_frame(frame, index, blur_size, brightness);
|
||||
for u in 0..LinkyFrame::universes() {
|
||||
let mut packet = ArtNetDMX::new();
|
||||
packet.set_universe(u);
|
||||
packet.unset_seq();
|
||||
|
||||
let linky_data = linky_frame.get_universe_data(u);
|
||||
packet.set_value_full(&linky_data);
|
||||
|
||||
file.write(&packet.data).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
process_frames(&matches.opt_str("f").unwrap(), frame_callback, false).unwrap();
|
||||
} else {
|
||||
print_usage(program, options);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_usage(program: String, options: Options) {
|
||||
let brief = format!("Usage: {} -f FILE [options]", program);
|
||||
print!("{}", options.usage(&brief));
|
||||
}
|
||||
Reference in New Issue
Block a user