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