Initial commit
This commit is contained in:
commit
8ab8cdedbd
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/target
|
||||||
|
*.svg
|
||||||
|
*.data
|
||||||
|
*.old
|
||||||
|
/bin
|
16
Cargo.lock
generated
Normal file
16
Cargo.lock
generated
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "live-link-face"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "live-link-face"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Klink"]
|
||||||
|
description = "Live Link Face packets encoder/decoder"
|
||||||
|
repository = "https://git.zhitno.st/klink/live-link-face"
|
||||||
|
keywords = ["unreal", "livelink", "face-tracking"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
|
||||||
|
[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*
|
8
LICENSE
Normal file
8
LICENSE
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Copyright (C) 2024 Klink
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
|
||||||
|
|
||||||
|
The above copyright notice, this permission notice and the word "NIGGER" shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>
|
11
README.md
Normal file
11
README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Live Link Face Rust
|
||||||
|
|
||||||
|
Live Link Face Rust is a library for decoding/encoding UDP dadagrams sent by Live Link Face App.
|
||||||
|
You get `LiveLinkFace` struct from which you can get any blend shape:
|
||||||
|
```
|
||||||
|
let face_data = LiveLinkFace::decode(&BUF); // UDP datagram; decode returns tuple of (bool, LiveLinkFace)
|
||||||
|
assert!(face_data.0); // 'true' means that there is face data. false - returns empty LiveLinkFace object
|
||||||
|
let eye_blink_left = face_data.1.get_blendshape(FaceBlendShape::EyeBlinkLeft);
|
||||||
|
```
|
||||||
|
|
||||||
|
This library is inspired by https://github.com/JimWest/PyLiveLinkFace
|
270
examples/listener.rs
Normal file
270
examples/listener.rs
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
use live_link_face::{LiveLinkFace, FaceBlendShape};
|
||||||
|
use std::io::{prelude::*, stdout};
|
||||||
|
use std::net::UdpSocket;
|
||||||
|
|
||||||
|
fn main() -> std::io::Result<()> {
|
||||||
|
let socket = UdpSocket::bind("[::]:3000")?; // for UDP4/6
|
||||||
|
let mut buf = [0; 1024];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Receives a single datagram message on the socket.
|
||||||
|
// If `buf` is too small to hold
|
||||||
|
// the message, it will be cut off.
|
||||||
|
let (amt, _src) = socket.recv_from(&mut buf)?;
|
||||||
|
|
||||||
|
// Redeclare `buf` as slice of the received data
|
||||||
|
// and send data back to origin.
|
||||||
|
let buf = &mut buf[..amt];
|
||||||
|
let face = LiveLinkFace::decode(buf);
|
||||||
|
shitty_print(face.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shitty_print(face_data: LiveLinkFace) {
|
||||||
|
print!("{}[2J", 27 as char);
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
println!(
|
||||||
|
"EyeBlinkLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeBlinkLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookDownLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookDownLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookInLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookInLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookOutLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookOutLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookUpLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookUpLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeSquintLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeSquintLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeWideLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeWideLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeBlinkRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeBlinkRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookDownRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookDownRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookInRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookInRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookOutRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookOutRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeLookUpRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeLookUpRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeSquintRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeSquintRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"EyeWideRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::EyeWideRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"JawForward: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::JawForward)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"JawLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::JawLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"JawRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::JawRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"JawOpen: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::JawOpen)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthClose: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthClose)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthFunnel: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthFunnel)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthPucker: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthPucker)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthSmileLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthSmileLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthSmileRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthSmileRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthFrownLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthFrownLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthFrownRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthFrownRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthDimpleLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthDimpleLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthDimpleRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthDimpleRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthStretchLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthStretchLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthStretchRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthStretchRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthRollLower: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthRollLower)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthRollUpper: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthRollUpper)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthShrugLower: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthShrugLower)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthShrugUpper: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthShrugUpper)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthPressLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthPressLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthPressRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthPressRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthLowerDownLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthLowerDownLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthLowerDownRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthLowerDownRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthUpperUpLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthUpperUpLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"MouthUpperUpRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::MouthUpperUpRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"BrowDownLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::BrowDownLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"BrowDownRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::BrowDownRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"BrowInnerUp: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::BrowInnerUp)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"BrowOuterUpLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::BrowOuterUpLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"BrowOuterUpRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::BrowOuterUpRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"CheekPuff: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::CheekPuff)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"CheekSquintLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::CheekSquintLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"CheekSquintRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::CheekSquintRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"NoseSneerLeft: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::NoseSneerLeft)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"NoseSneerRight: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::NoseSneerRight)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"TongueOut: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::TongueOut)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"HeadYaw: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::HeadYaw)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"HeadPitch: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::HeadPitch)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"HeadRoll: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::HeadRoll)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"LeftEyeYaw: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::LeftEyeYaw)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"LeftEyePitch: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::LeftEyePitch)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"LeftEyeRoll: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::LeftEyeRoll)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"RightEyeYaw: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::RightEyeYaw)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"RightEyePitch: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::RightEyePitch)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"RightEyeRoll: {}",
|
||||||
|
face_data.get_blendshape(FaceBlendShape::RightEyeRoll)
|
||||||
|
);
|
||||||
|
}
|
93
flake.lock
Normal file
93
flake.lock
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705309234,
|
||||||
|
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1698420672,
|
||||||
|
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708296515,
|
||||||
|
"narHash": "sha256-FyF489fYNAUy7b6dkYV6rGPyzp+4tThhr80KNAaF/yY=",
|
||||||
|
"path": "/nix/store/2p9qcybzmx1rfayjw866rz8xljbw45g9-source",
|
||||||
|
"rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa",
|
||||||
|
"type": "path"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708407374,
|
||||||
|
"narHash": "sha256-EECzarm+uqnNDCwaGg/ppXCO11qibZ1iigORShkkDf0=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "f33dd27a47ebdf11dc8a5eb05e7c8fbdaf89e73f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
42
flake.nix
Normal file
42
flake.nix
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
naersk.url = "github:nix-community/naersk";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, flake-utils, naersk, nixpkgs }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = (import nixpkgs) { inherit system; };
|
||||||
|
|
||||||
|
naersk' = pkgs.callPackage naersk { };
|
||||||
|
|
||||||
|
in rec {
|
||||||
|
# For `nix build` & `nix run`:
|
||||||
|
packages.default = naersk'.buildPackage {
|
||||||
|
src = ./.;
|
||||||
|
nativeBuildInputs = with pkgs; [ pkg-config openssl ];
|
||||||
|
GIT_HASH = "000000000000000000000000000000";
|
||||||
|
};
|
||||||
|
|
||||||
|
# For `nix develop`:
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
rustc
|
||||||
|
cargo
|
||||||
|
cargo-watch
|
||||||
|
clippy
|
||||||
|
rustfmt
|
||||||
|
rust-analyzer
|
||||||
|
pkg-config
|
||||||
|
|
||||||
|
openssl
|
||||||
|
];
|
||||||
|
env = {
|
||||||
|
RUST_BACKTRACE = 1;
|
||||||
|
RUST_LOG = "debug";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
240
src/lib.rs
Normal file
240
src/lib.rs
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
const FILTER_SIZE: usize = 5;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum FaceBlendShape {
|
||||||
|
EyeBlinkLeft = 0,
|
||||||
|
EyeLookDownLeft = 1,
|
||||||
|
EyeLookInLeft = 2,
|
||||||
|
EyeLookOutLeft = 3,
|
||||||
|
EyeLookUpLeft = 4,
|
||||||
|
EyeSquintLeft = 5,
|
||||||
|
EyeWideLeft = 6,
|
||||||
|
EyeBlinkRight = 7,
|
||||||
|
EyeLookDownRight = 8,
|
||||||
|
EyeLookInRight = 9,
|
||||||
|
EyeLookOutRight = 10,
|
||||||
|
EyeLookUpRight = 11,
|
||||||
|
EyeSquintRight = 12,
|
||||||
|
EyeWideRight = 13,
|
||||||
|
JawForward = 14,
|
||||||
|
JawLeft = 15,
|
||||||
|
JawRight = 16,
|
||||||
|
JawOpen = 17,
|
||||||
|
MouthClose = 18,
|
||||||
|
MouthFunnel = 19,
|
||||||
|
MouthPucker = 20,
|
||||||
|
MouthLeft = 21,
|
||||||
|
MouthRight = 22,
|
||||||
|
MouthSmileLeft = 23,
|
||||||
|
MouthSmileRight = 24,
|
||||||
|
MouthFrownLeft = 25,
|
||||||
|
MouthFrownRight = 26,
|
||||||
|
MouthDimpleLeft = 27,
|
||||||
|
MouthDimpleRight = 28,
|
||||||
|
MouthStretchLeft = 29,
|
||||||
|
MouthStretchRight = 30,
|
||||||
|
MouthRollLower = 31,
|
||||||
|
MouthRollUpper = 32,
|
||||||
|
MouthShrugLower = 33,
|
||||||
|
MouthShrugUpper = 34,
|
||||||
|
MouthPressLeft = 35,
|
||||||
|
MouthPressRight = 36,
|
||||||
|
MouthLowerDownLeft = 37,
|
||||||
|
MouthLowerDownRight = 38,
|
||||||
|
MouthUpperUpLeft = 39,
|
||||||
|
MouthUpperUpRight = 40,
|
||||||
|
BrowDownLeft = 41,
|
||||||
|
BrowDownRight = 42,
|
||||||
|
BrowInnerUp = 43,
|
||||||
|
BrowOuterUpLeft = 44,
|
||||||
|
BrowOuterUpRight = 45,
|
||||||
|
CheekPuff = 46,
|
||||||
|
CheekSquintLeft = 47,
|
||||||
|
CheekSquintRight = 48,
|
||||||
|
NoseSneerLeft = 49,
|
||||||
|
NoseSneerRight = 50,
|
||||||
|
TongueOut = 51,
|
||||||
|
HeadYaw = 52,
|
||||||
|
HeadPitch = 53,
|
||||||
|
HeadRoll = 54,
|
||||||
|
LeftEyeYaw = 55,
|
||||||
|
LeftEyePitch = 56,
|
||||||
|
LeftEyeRoll = 57,
|
||||||
|
RightEyeYaw = 58,
|
||||||
|
RightEyePitch = 59,
|
||||||
|
RightEyeRoll = 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LiveLinkFace {
|
||||||
|
pub uuid: String,
|
||||||
|
pub name: String,
|
||||||
|
pub fps: i32,
|
||||||
|
version: u32,
|
||||||
|
frames: u32,
|
||||||
|
sub_frame: i32,
|
||||||
|
denominator: i32,
|
||||||
|
blend_shapes: Vec<f32>,
|
||||||
|
old_blend_shapes: Vec<VecDeque<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveLinkFace {
|
||||||
|
pub fn new(name: String, uuid: String, fps: i32) -> Self {
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards");
|
||||||
|
let frames = now.as_secs() as u32;
|
||||||
|
let sub_frame = 1056060032; // I don't know why
|
||||||
|
let denominator = fps / 60; // 1 most of the time
|
||||||
|
|
||||||
|
let mut old_blend_shapes: Vec<VecDeque<f32>> = Vec::with_capacity(61);
|
||||||
|
for _ in 0..61 {
|
||||||
|
old_blend_shapes.push(VecDeque::with_capacity(FILTER_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveLinkFace {
|
||||||
|
uuid,
|
||||||
|
name,
|
||||||
|
fps,
|
||||||
|
version: 6,
|
||||||
|
frames,
|
||||||
|
sub_frame,
|
||||||
|
denominator,
|
||||||
|
blend_shapes: vec![0.0; 61],
|
||||||
|
old_blend_shapes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(&self) -> Vec<u8> {
|
||||||
|
let mut encoded: Vec<u8> = Vec::new();
|
||||||
|
encoded.extend(&(self.version).to_le_bytes());
|
||||||
|
encoded.extend(self.uuid.as_bytes());
|
||||||
|
encoded.extend(&(self.name.len() as i32).to_be_bytes());
|
||||||
|
encoded.extend(self.name.as_bytes());
|
||||||
|
|
||||||
|
// Prepare frame data
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards");
|
||||||
|
let frame_time = now.as_secs() as u32;
|
||||||
|
let frame_rate = (self.fps, self.denominator);
|
||||||
|
let blend_shapes_data = self.blend_shapes.to_vec();
|
||||||
|
|
||||||
|
encoded.extend(&(frame_time).to_be_bytes());
|
||||||
|
encoded.extend(&(self.sub_frame).to_be_bytes());
|
||||||
|
encoded.extend(&(frame_rate).0.to_be_bytes());
|
||||||
|
encoded.extend(&(frame_rate).1.to_be_bytes());
|
||||||
|
encoded.extend(&(blend_shapes_data.len() as i32).to_be_bytes());
|
||||||
|
for f in &blend_shapes_data {
|
||||||
|
encoded.extend(&f.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_blendshape(&self, index: FaceBlendShape) -> f32 {
|
||||||
|
self.blend_shapes[index as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_blendshape(&mut self, index: FaceBlendShape, value: f32, no_filter: bool) {
|
||||||
|
let idx = index as usize;
|
||||||
|
if no_filter {
|
||||||
|
self.blend_shapes[idx] = value;
|
||||||
|
} else {
|
||||||
|
self.old_blend_shapes[idx].push_back(value);
|
||||||
|
let filtered_value: f32 = self.old_blend_shapes[idx].iter().sum::<f32>()
|
||||||
|
/ self.old_blend_shapes[idx].len() as f32;
|
||||||
|
self.blend_shapes[idx] = filtered_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(data: &[u8]) -> (bool, LiveLinkFace) {
|
||||||
|
let version = LittleEndian::read_i16(&data[0..4]);
|
||||||
|
let uuid = String::from_utf8_lossy(&data[4..41]).to_string();
|
||||||
|
let name_length = BigEndian::read_i32(&data[41..45]) as usize;
|
||||||
|
let name_end_pos = 45 + name_length;
|
||||||
|
let name = String::from_utf8_lossy(&data[45..name_end_pos]).to_string();
|
||||||
|
|
||||||
|
if data.len() > name_end_pos + 16 {
|
||||||
|
let frame_number = BigEndian::read_i32(&data[name_end_pos..name_end_pos + 4]) as usize;
|
||||||
|
let sub_frame = BigEndian::read_i32(&data[name_end_pos + 4..name_end_pos + 8]);
|
||||||
|
let fps = BigEndian::read_i32(&data[name_end_pos + 8..name_end_pos + 12]);
|
||||||
|
let denominator = BigEndian::read_i32(&data[name_end_pos + 12..name_end_pos + 16]);
|
||||||
|
let (int_bytes, _rest) = data[70..74].split_at(std::mem::size_of::<i8>());
|
||||||
|
let data_length = i8::from_be_bytes(int_bytes.try_into().unwrap());
|
||||||
|
|
||||||
|
if data_length != 61 {
|
||||||
|
println!(
|
||||||
|
"Blendshape data is malformed. got: {}; requred: 61",
|
||||||
|
data_length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut blend_shapes: [f32; 61] = [0.0; 61];
|
||||||
|
for i in 0..61 {
|
||||||
|
blend_shapes[i] = BigEndian::read_f32(
|
||||||
|
&data[name_end_pos + 17 + i * 4..name_end_pos + 17 + (i + 1) * 4],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("vata: {:?}", vata);
|
||||||
|
let mut live_link_face = LiveLinkFace::new(name, uuid, fps);
|
||||||
|
live_link_face.version = version as u32;
|
||||||
|
live_link_face.frames = frame_number as u32;
|
||||||
|
live_link_face.sub_frame = sub_frame;
|
||||||
|
live_link_face.denominator = denominator;
|
||||||
|
live_link_face.blend_shapes = blend_shapes.to_vec();
|
||||||
|
|
||||||
|
return (true, live_link_face);
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
LiveLinkFace::new("Default".to_string(), "Default".to_string(), 60),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{LiveLinkFace, FaceBlendShape};
|
||||||
|
|
||||||
|
const BUF: [u8;315] = [
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x24, 0x30, 0x30, 0x46, 0x44, 0x45, 0x31, 0x43, 0x33, 0x2D,
|
||||||
|
0x46, 0x41, 0x38, 0x35, 0x2D, 0x34, 0x30, 0x36, 0x37, 0x2D, 0x39, 0x37, 0x44, 0x38,
|
||||||
|
0x2D, 0x31, 0x31, 0x33, 0x37, 0x43, 0x41, 0x31, 0x38, 0x33, 0x30, 0x35, 0x45, 0x00,
|
||||||
|
0x00, 0x00, 0x09, 0x46, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x63, 0x6B, 0x00, 0x00,
|
||||||
|
0xFD, 0x2F, 0x3E, 0x02, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x3D, 0x39, 0xC0, 0x37, 0x00, 0x3D, 0x95, 0x3A, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x39, 0xC0, 0x95, 0xF3, 0x3D, 0x95, 0x2D, 0x4E, 0x3D, 0x02, 0xA5, 0x4E, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x39, 0x7B, 0x26, 0xB0, 0x3B, 0x0D, 0x5D, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x3B,
|
||||||
|
0x1D, 0xB8, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x3B, 0xE3, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xFD, 0xE5,
|
||||||
|
0x7A, 0x3B, 0x09, 0x19, 0x78, 0x3B, 0x64, 0x88, 0xAE, 0x3B, 0x2E, 0x7D, 0xE2, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x3A, 0xD8, 0xD2, 0xAF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBD,
|
||||||
|
0xE8, 0xCC, 0x08, 0xBE, 0x39, 0x6A, 0x4C, 0x3C, 0x36, 0xE9, 0x45, 0xBC, 0x9D, 0xC5,
|
||||||
|
0x24, 0x3D, 0x2A, 0x20, 0x39, 0xBA, 0xE9, 0xF6, 0xC7, 0xBC, 0x9B, 0x6C, 0xD2, 0x3D,
|
||||||
|
0x2A, 0x1E, 0x4B, 0x3A, 0xA1, 0xB0, 0x42,
|
||||||
|
];
|
||||||
|
#[test]
|
||||||
|
fn decode_buf() {
|
||||||
|
let face_data = LiveLinkFace::decode(&BUF);
|
||||||
|
|
||||||
|
assert!(face_data.0);
|
||||||
|
assert_eq!(face_data.1.get_blendshape(FaceBlendShape::EyeBlinkLeft), 0.00036662072);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue