From 4d3e531141e3ffe534b8e15185e0628a4809a0ee Mon Sep 17 00:00:00 2001 From: Baud Date: Tue, 23 Jan 2024 23:19:40 +0000 Subject: [PATCH] Initial Commit --- .gitignore | 1 + Cargo.lock | 601 +++++++++++++++++ Cargo.toml | 6 + atem-connection-rs/Cargo.lock | 199 ++++++ atem-connection-rs/Cargo.toml | 11 + atem-connection-rs/src/atem.rs | 19 + .../src/atem_lib/atem_packet.rs | 95 +++ .../src/atem_lib/atem_socket.rs | 68 ++ .../src/atem_lib/atem_socket_inner.rs | 473 +++++++++++++ atem-connection-rs/src/atem_lib/atem_util.rs | 4 + atem-connection-rs/src/atem_lib/mod.rs | 4 + .../src/commands/command_base.rs | 29 + atem-connection-rs/src/commands/mod.rs | 1 + atem-connection-rs/src/enums/mod.rs | 410 ++++++++++++ atem-connection-rs/src/lib.rs | 13 + atem-connection-rs/src/state/atem_macro.rs | 28 + atem-connection-rs/src/state/audio.rs | 57 ++ atem-connection-rs/src/state/color.rs | 6 + atem-connection-rs/src/state/common.rs | 8 + atem-connection-rs/src/state/fairlight.rs | 141 ++++ atem-connection-rs/src/state/info.rs | 86 +++ atem-connection-rs/src/state/input.rs | 14 + atem-connection-rs/src/state/media.rs | 40 ++ atem-connection-rs/src/state/mod.rs | 31 + atem-connection-rs/src/state/recording.rs | 41 ++ atem-connection-rs/src/state/settings.rs | 61 ++ atem-connection-rs/src/state/streaming.rs | 32 + atem-connection-rs/src/state/util.rs | 9 + .../src/state/video/downstream_keyers.rs | 111 +++ atem-connection-rs/src/state/video/mod.rs | 115 ++++ .../src/state/video/super_source.rs | 51 ++ .../src/state/video/upstream_keyers.rs | 632 ++++++++++++++++++ atem-test/Cargo.toml | 12 + atem-test/src/main.rs | 44 ++ 34 files changed, 3453 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 atem-connection-rs/Cargo.lock create mode 100644 atem-connection-rs/Cargo.toml create mode 100644 atem-connection-rs/src/atem.rs create mode 100755 atem-connection-rs/src/atem_lib/atem_packet.rs create mode 100644 atem-connection-rs/src/atem_lib/atem_socket.rs create mode 100644 atem-connection-rs/src/atem_lib/atem_socket_inner.rs create mode 100644 atem-connection-rs/src/atem_lib/atem_util.rs create mode 100644 atem-connection-rs/src/atem_lib/mod.rs create mode 100644 atem-connection-rs/src/commands/command_base.rs create mode 100644 atem-connection-rs/src/commands/mod.rs create mode 100644 atem-connection-rs/src/enums/mod.rs create mode 100644 atem-connection-rs/src/lib.rs create mode 100644 atem-connection-rs/src/state/atem_macro.rs create mode 100644 atem-connection-rs/src/state/audio.rs create mode 100644 atem-connection-rs/src/state/color.rs create mode 100644 atem-connection-rs/src/state/common.rs create mode 100644 atem-connection-rs/src/state/fairlight.rs create mode 100644 atem-connection-rs/src/state/info.rs create mode 100644 atem-connection-rs/src/state/input.rs create mode 100644 atem-connection-rs/src/state/media.rs create mode 100644 atem-connection-rs/src/state/mod.rs create mode 100644 atem-connection-rs/src/state/recording.rs create mode 100644 atem-connection-rs/src/state/settings.rs create mode 100644 atem-connection-rs/src/state/streaming.rs create mode 100644 atem-connection-rs/src/state/util.rs create mode 100644 atem-connection-rs/src/state/video/downstream_keyers.rs create mode 100644 atem-connection-rs/src/state/video/mod.rs create mode 100644 atem-connection-rs/src/state/video/super_source.rs create mode 100644 atem-connection-rs/src/state/video/upstream_keyers.rs create mode 100644 atem-test/Cargo.toml create mode 100644 atem-test/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b60de5b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cc778e5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,601 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atem-connection-rs" +version = "0.1.0" +dependencies = [ + "derive-getters", + "derive-new", + "log", + "thiserror", + "tokio", +] + +[[package]] +name = "atem-test" +version = "0.1.0" +dependencies = [ + "atem-connection-rs", + "color-eyre", + "env_logger", + "tokio", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color-eyre" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "derive-getters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c5905670fd9c320154f3a4a01c9e609733cd7b753f3c58777ab7d5ce26686b3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "gimli" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "owo-colors" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "syn" +version = "1.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1fa8b0e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "atem-connection-rs", + "atem-test" +] diff --git a/atem-connection-rs/Cargo.lock b/atem-connection-rs/Cargo.lock new file mode 100644 index 0000000..7ae146f --- /dev/null +++ b/atem-connection-rs/Cargo.lock @@ -0,0 +1,199 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +dependencies = [ + "memchr", +] + +[[package]] +name = "atem-connection-rs" +version = "0.1.0" +dependencies = [ + "cargo-add", + "derive-getters", + "derive-new", +] + +[[package]] +name = "cargo-add" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cf2d539c704d9d46cc7b324c44d3cab064a84e98f76d613314bac30117b9e4" +dependencies = [ + "docopt", + "rustc-serialize", + "semver", + "toml", +] + +[[package]] +name = "derive-getters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c5905670fd9c320154f3a4a01c9e609733cd7b753f3c58777ab7d5ce26686b3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "docopt" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8" +dependencies = [ + "lazy_static 0.2.11", + "regex", + "rustc-serialize", + "strsim", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", + "utf8-ranges", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "strsim" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" + +[[package]] +name = "syn" +version = "1.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "toml" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" +dependencies = [ + "rustc-serialize", +] + +[[package]] +name = "ucd-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85f514e095d348c279b1e5cd76795082cf15bd59b93207832abe0b1d8fed236" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" diff --git a/atem-connection-rs/Cargo.toml b/atem-connection-rs/Cargo.toml new file mode 100644 index 0000000..96185cf --- /dev/null +++ b/atem-connection-rs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "atem-connection-rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +derive-getters = "0.2.0" +derive-new = "0.5.9" +log = "0.4.14" +thiserror = "1.0.30" +tokio = { version = "1.13.0", features = ["full"] } diff --git a/atem-connection-rs/src/atem.rs b/atem-connection-rs/src/atem.rs new file mode 100644 index 0000000..c293e7f --- /dev/null +++ b/atem-connection-rs/src/atem.rs @@ -0,0 +1,19 @@ +use crate::{commands::command_base::IDeserializedCommand, state::AtemState}; + +pub struct AtemOptions { + address: Option, + port: Option, + debug_buffers: bool, + disable_multi_threaded: bool, + child_process_timeout: Option, +} + +pub enum AtemEvents { + Error(String), + Info(String), + Debug(String), + Connected, + Disconnected, + StateChanged(Box<(AtemState, Vec)>), + ReceivedCommands(Vec>), +} diff --git a/atem-connection-rs/src/atem_lib/atem_packet.rs b/atem-connection-rs/src/atem_lib/atem_packet.rs new file mode 100755 index 0000000..18a9b02 --- /dev/null +++ b/atem-connection-rs/src/atem_lib/atem_packet.rs @@ -0,0 +1,95 @@ +pub struct AtemPacket { + length: u16, + flags: u8, + session_id: u16, + remote_packet_id: u16, + body: Vec, +} + +pub enum AtemPacketErr { + TooShort(String), + LengthDiffers(String), +} + +#[derive(PartialEq)] +pub enum PacketFlag { + AckRequest, + NewSessionId, + IsRetransmit, + RetransmitRequest, + AckReply, +} + +impl From for u8 { + fn from(flag: PacketFlag) -> Self { + match flag { + PacketFlag::AckRequest => 0x01, + PacketFlag::NewSessionId => 0x02, + PacketFlag::IsRetransmit => 0x04, + PacketFlag::RetransmitRequest => 0x08, + PacketFlag::AckReply => 0x10, + } + } +} + +impl AtemPacket { + pub fn length(&self) -> u16 { + self.length + } + + pub fn flags(&self) -> u8 { + self.flags + } + + pub fn session_id(&self) -> u16 { + self.session_id + } + + pub fn remote_packet_id(&self) -> u16 { + self.remote_packet_id + } + + pub fn body(&self) -> Vec { + self.body.clone() + } + + pub fn has_flag(&self, flag: PacketFlag) -> bool { + self.flags & u8::from(flag) > 0 + } +} + +impl TryFrom<&[u8]> for AtemPacket { + type Error = AtemPacketErr; + + fn try_from(buffer: &[u8]) -> Result { + if buffer.len() < 12 { + return Err(AtemPacketErr::TooShort(format!( + "Invalid packet from ATEM {:x?}", + buffer + ))); + } + + let length = u16::from_be_bytes(buffer[0..2].try_into().unwrap()) & 0x07ff; + if length as usize != buffer.len() { + return Err(AtemPacketErr::LengthDiffers(format!( + "Length of message differs, expected {} got {}", + length, + buffer.len() + ))); + } + + let flags = buffer[0] >> 3; + let session_id = u16::from_be_bytes(buffer[2..4].try_into().unwrap()); + let remote_packet_id = u16::from_be_bytes(buffer[10..12].try_into().unwrap()); + + let body = buffer[12..].to_vec(); + + Ok(AtemPacket { + length, + flags, + session_id, + remote_packet_id, + body, + }) + } +} diff --git a/atem-connection-rs/src/atem_lib/atem_socket.rs b/atem-connection-rs/src/atem_lib/atem_socket.rs new file mode 100644 index 0000000..e1b5d4e --- /dev/null +++ b/atem-connection-rs/src/atem_lib/atem_socket.rs @@ -0,0 +1,68 @@ +use std::{io, sync::Arc, thread::yield_now}; + +use tokio::{sync::RwLock, task::JoinHandle}; + +use super::atem_socket_inner::AtemSocketInner; + +pub struct AtemSocket { + socket: Arc>, + + inner_socket_handle: JoinHandle<()>, +} + +#[derive(Debug, Error)] +pub enum AtemSocketConnectionError { + #[error("Socket connection error")] + IoError(#[from] io::Error), +} + +impl AtemSocket { + pub fn new() -> Self { + let socket = AtemSocketInner::new(); + let socket = Arc::new(RwLock::new(socket)); + + let socket_clone = Arc::clone(&socket); + let handle = tokio::spawn(async move { + loop { + socket_clone.write().await.tick().await; + + yield_now(); + } + }); + + AtemSocket { + socket, + + inner_socket_handle: handle, + } + } + + pub async fn connect( + &mut self, + address: String, + port: u16, + ) -> Result<(), AtemSocketConnectionError> { + self.socket.write().await.connect(address, port).await?; + + Ok(()) + } + + pub async fn disconnect(self) { + self.inner_socket_handle.abort(); + self.socket.write().await.disconnect() + } + + pub async fn send_command(&mut self, payload: &[u8], raw_name: &str, tracking_id: u64) { + self.socket + .write() + .await + .send_command(payload, raw_name, tracking_id) + .await; + } +} + +impl Default for AtemSocket { + fn default() -> Self { + Self::new() + } +} diff --git a/atem-connection-rs/src/atem_lib/atem_socket_inner.rs b/atem-connection-rs/src/atem_lib/atem_socket_inner.rs new file mode 100644 index 0000000..f1e8747 --- /dev/null +++ b/atem-connection-rs/src/atem_lib/atem_socket_inner.rs @@ -0,0 +1,473 @@ +use std::{ + io, + net::SocketAddr, + time::{Duration, SystemTime}, +}; + +use log::debug; +use tokio::net::UdpSocket; + +use crate::atem_lib::atem_util; + +const IN_FLIGHT_TIMEOUT: u64 = 60; +const CONNECTION_TIMEOUT: u64 = 5000; +const CONNECTION_RETRY_INTERVAL: u64 = 1000; +const RETRANSMIT_CHECK_INTERVAL: u64 = 1000; +const MAX_PACKET_RETRIES: u16 = 10; +const MAX_PACKET_ID: u16 = 1 << 15; +const MAX_PACKET_PER_ACK: u16 = 16; + +// Set to max UDP packet size, for now +const MAX_PACKET_RECEIVE_SIZE: usize = 65535; +const ACK_PACKET_LENGTH: u16 = 12; + +#[derive(PartialEq, Clone)] +enum ConnectionState { + Closed, + SynSent, + Established, +} + +#[allow(clippy::from_over_into)] +impl Into for ConnectionState { + fn into(self) -> u8 { + match self { + ConnectionState::Closed => 0x00, + ConnectionState::SynSent => 0x01, + ConnectionState::Established => 0x02, + } + } +} + +#[derive(PartialEq)] +enum PacketFlag { + AckRequest, + NewSessionId, + IsRetransmit, + RetransmitRequest, + AckReply, +} + +impl From for u8 { + fn from(flag: PacketFlag) -> Self { + match flag { + PacketFlag::AckRequest => 0x01, + PacketFlag::NewSessionId => 0x02, + PacketFlag::IsRetransmit => 0x04, + PacketFlag::RetransmitRequest => 0x08, + PacketFlag::AckReply => 0x10, + } + } +} + +#[derive(Clone)] +struct InFlightPacket { + packet_id: u16, + tracking_id: u64, + payload: Vec, + pub last_sent: SystemTime, + pub resent: u16, +} + +struct AckedPacket { + packet_id: u16, + tracking_id: u64, +} + +pub struct AtemSocketCommand { + payload: Vec, + raw_name: String, + tracking_id: u64, +} + +pub struct AtemSocketInner { + connection_state: ConnectionState, + reconnect_timer: Option, + retransmit_timer: Option, + + next_send_packet_id: u16, + session_id: u16, + + socket: Option, + address: String, + port: u16, + + last_received_at: SystemTime, + last_received_packed_id: u16, + in_flight: Vec, + ack_timer: Option, + received_without_ack: u16, +} + +enum AtemSocketReceiveError { + Closed, +} + +#[derive(Debug, Error)] +enum AtemSocketWriteError { + #[error("Socket closed")] + Closed, + + #[error("Socket disconnected")] + Disconnected(#[from] io::Error), +} + +impl AtemSocketInner { + pub fn new() -> Self { + AtemSocketInner { + connection_state: ConnectionState::Closed, + reconnect_timer: None, + retransmit_timer: None, + + next_send_packet_id: 1, + session_id: 0, + + socket: None, + address: "0.0.0.0".to_string(), + port: 0, + + last_received_at: SystemTime::now(), + last_received_packed_id: 0, + in_flight: vec![], + ack_timer: None, + received_without_ack: 0, + } + } + + pub async fn connect(&mut self, address: String, port: u16) -> Result<(), io::Error> { + self.address = address.clone(); + self.port = port; + + let socket = UdpSocket::bind("0.0.0.0:0").await?; + let remote_addr = format!("{}:{}", address, port) + .parse::() + .unwrap(); + socket.connect(remote_addr).await?; + self.socket = Some(socket); + + self.start_timers(); + + self.next_send_packet_id = 1; + self.session_id = 0; + self.in_flight = vec![]; + debug!("Reconnect"); + + self.send_packet(&atem_util::COMMAND_CONNECT_HELLO).await; + self.connection_state = ConnectionState::SynSent; + + Ok(()) + } + + pub fn disconnect(&mut self) { + self.stop_timers(); + + self.retransmit_timer = None; + self.reconnect_timer = None; + self.ack_timer = None; + self.socket = None; + + let prev_connection_state = self.connection_state.clone(); + self.connection_state = ConnectionState::Closed; + + if prev_connection_state == ConnectionState::Established { + self.on_disconnect(); + } + } + + pub async fn send_commands(&mut self, commands: Vec) { + for command in commands.into_iter() { + self.send_command(&command.payload, &command.raw_name, command.tracking_id) + .await; + } + } + + pub async fn send_command(&mut self, payload: &[u8], raw_name: &str, tracking_id: u64) { + let packet_id = self.next_send_packet_id; + self.next_send_packet_id += 1; + if self.next_send_packet_id >= MAX_PACKET_ID { + self.next_send_packet_id = 0; + } + + let opcode = u16::from(u8::from(PacketFlag::AckRequest)) << 11; + + let mut buffer = vec![0; 20 + payload.len()]; + + // Headers + buffer[0..2].copy_from_slice(&u16::to_be_bytes(opcode | (payload.len() as u16 + 20))); + buffer[2..4].copy_from_slice(&u16::to_be_bytes(self.session_id)); + buffer[10..12].copy_from_slice(&u16::to_be_bytes(packet_id)); + + // Command + buffer[12..14].copy_from_slice(&u16::to_be_bytes(payload.len() as u16 + 8)); + buffer[16..20].copy_from_slice(raw_name.as_bytes()); + + // Body + buffer[20..20 + payload.len()].copy_from_slice(payload); + self.send_packet(&buffer).await; + + debug!("{:x?}", buffer); + + self.in_flight.push(InFlightPacket { + packet_id, + tracking_id, + payload: buffer, + last_sent: SystemTime::now(), + resent: 0, + }) + } + + async fn restart_connection(&mut self) { + self.disconnect(); + self.connect(self.address.clone(), self.port).await.ok(); + } + + pub async fn tick(&mut self) { + let messages = self.receive().await.ok(); + if let Some(messages) = messages { + for message in messages.iter() { + self.recieved_packet(message).await; + } + } + if let Some(ack_time) = self.ack_timer { + if ack_time <= SystemTime::now() { + self.ack_timer = None; + self.received_without_ack = 0; + self.send_ack(self.last_received_packed_id).await; + } + } + if let Some(reconnect_time) = self.reconnect_timer { + if reconnect_time <= SystemTime::now() { + if self.last_received_at + Duration::from_millis(CONNECTION_TIMEOUT) + <= SystemTime::now() + { + debug!("{:?}", self.last_received_at); + debug!("Connection timed out, restarting"); + self.restart_connection().await; + } + self.start_reconnect_timer(); + } + } + if let Some(retransmit_time) = self.retransmit_timer { + if retransmit_time <= SystemTime::now() { + self.check_for_retransmit().await; + self.start_retransmit_timer(); + } + } + } + + async fn receive(&mut self) -> Result>, AtemSocketReceiveError> { + let mut messages: Vec> = vec![]; + let socket = self.socket.as_mut().ok_or(AtemSocketReceiveError::Closed)?; + + let mut buf = [0; MAX_PACKET_RECEIVE_SIZE]; + if let Ok((message_size, _)) = socket.try_recv_from(&mut buf) { + messages.push(buf[0..message_size].to_owned()); + } + + Ok(messages) + } + + fn is_packet_covered_by_ack(&self, ack_id: u16, packet_id: u16) -> bool { + let tolerance: u16 = MAX_PACKET_ID / 2; + let pkt_is_shortly_before = packet_id < ack_id && packet_id + tolerance > ack_id; + let pkt_is_shortly_after = packet_id > ack_id && packet_id < ack_id + tolerance; + let pkt_is_before_wrap = packet_id > ack_id + tolerance; + packet_id == ack_id + || ((pkt_is_shortly_before || pkt_is_before_wrap) && !pkt_is_shortly_after) + } + + async fn recieved_packet(&mut self, packet: &[u8]) { + debug!("RECV {:x?}", packet); + + if packet.len() < 12 { + debug!("Invalid packet from ATEM {:x?}", packet); + return; + } + + self.last_received_at = SystemTime::now(); + let length = u16::from_be_bytes(packet[0..2].try_into().unwrap()) & 0x07ff; + + if length as usize != packet.len() { + debug!( + "Length of message differs, expected {} got {}", + length, + packet.len() + ); + return; + } + + let flags = packet[0] >> 3; + self.session_id = u16::from_be_bytes(packet[2..4].try_into().unwrap()); + let remote_packet_id = u16::from_be_bytes(packet[10..12].try_into().unwrap()); + + if flags & u8::from(PacketFlag::NewSessionId) > 0 { + self.connection_state = ConnectionState::Established; + self.last_received_packed_id = remote_packet_id; + self.send_ack(remote_packet_id).await; + return; + } + + if self.connection_state == ConnectionState::Established { + if flags & u8::from(PacketFlag::RetransmitRequest) > 0 { + let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap()); + debug!("Retransmit request: {:x?}", from_packet_id); + + self.retransmit_from(from_packet_id).await; + } + + if flags & u8::from(PacketFlag::AckRequest) > 0 { + if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID { + self.last_received_packed_id = remote_packet_id; + self.send_or_queue_ack().await; + + if length > 12 { + self.on_command_received(&packet[12..], remote_packet_id); + } + } else if self + .is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id) + { + self.send_or_queue_ack().await; + } + } + + if flags & u8::from(PacketFlag::IsRetransmit) > 0 { + debug!("ATEM retransmitted packet {:x?}", remote_packet_id); + } + + if flags & u8::from(PacketFlag::AckReply) > 0 { + let ack_packet_id = u16::from_be_bytes(packet[4..6].try_into().unwrap()); + let mut acked_commands: Vec = vec![]; + + self.in_flight = self + .in_flight + .clone() + .into_iter() + .filter(|pkt| { + if self.is_packet_covered_by_ack(ack_packet_id, pkt.packet_id) { + acked_commands.push(AckedPacket { + packet_id: pkt.packet_id, + tracking_id: pkt.tracking_id, + }); + false + } else { + true + } + }) + .collect(); + self.on_command_acknowledged(acked_commands); + } + } + } + + async fn send_packet(&self, packet: &[u8]) { + debug!("Send {:x?}", packet); + if let Some(socket) = &self.socket { + socket.send(packet).await.ok(); + } else { + debug!("Socket is not open") + } + } + + async fn send_or_queue_ack(&mut self) { + self.received_without_ack += 1; + if self.received_without_ack >= MAX_PACKET_PER_ACK { + self.received_without_ack = 0; + self.ack_timer = None; + self.send_ack(self.last_received_packed_id).await; + } else if self.ack_timer.is_none() { + self.ack_timer = Some(SystemTime::now() + Duration::from_millis(5)); + } + } + + async fn send_ack(&mut self, packet_id: u16) { + debug!("Sending ack for packet {:x?}", packet_id); + let flag: u8 = PacketFlag::AckReply.into(); + let opcode = u16::from(flag) << 11; + let mut buffer: [u8; ACK_PACKET_LENGTH as _] = [0; 12]; + buffer[0..2].copy_from_slice(&u16::to_be_bytes(opcode as u16 | ACK_PACKET_LENGTH)); + buffer[2..4].copy_from_slice(&u16::to_be_bytes(self.session_id)); + buffer[4..6].copy_from_slice(&u16::to_be_bytes(packet_id)); + self.send_packet(&buffer).await; + } + + async fn retransmit_from(&mut self, from_id: u16) { + let from_id = from_id % MAX_PACKET_ID; + + if let Some(index) = self + .in_flight + .iter() + .position(|pkt| pkt.packet_id == from_id) + { + debug!( + "Resending from {} to {}", + from_id, + self.in_flight[self.in_flight.len() - 1].packet_id + ); + for i in index..self.in_flight.len() { + let mut sent_packet = self.in_flight[i].clone(); + if sent_packet.packet_id == from_id + || !self.is_packet_covered_by_ack(from_id, sent_packet.packet_id) + { + sent_packet.last_sent = SystemTime::now(); + sent_packet.resent += 1; + + self.send_packet(&sent_packet.payload).await; + } + } + } else { + debug!("Unable to resend: {}", from_id); + self.restart_connection().await; + } + } + + async fn check_for_retransmit(&mut self) { + for sent_packet in self.in_flight.clone() { + if sent_packet.last_sent + Duration::from_millis(IN_FLIGHT_TIMEOUT) < SystemTime::now() + { + if sent_packet.resent <= MAX_PACKET_RETRIES + && self + .is_packet_covered_by_ack(self.next_send_packet_id, sent_packet.packet_id) + { + debug!("Retransmit from timeout: {}", sent_packet.packet_id); + + self.retransmit_from(sent_packet.packet_id).await; + } else { + debug!("Packet timed out: {}", sent_packet.packet_id); + self.restart_connection().await; + } + } + } + } + + fn on_command_received(&mut self, payload: &[u8], packet_id: u16) { + // TODO: Emit some event + } + + fn on_command_acknowledged(&mut self, ids: Vec) { + // TODO: Emit some event + } + + fn on_disconnect(&mut self) { + // TODO: Emit some event + } + + fn start_timers(&mut self) { + self.start_reconnect_timer(); + self.start_retransmit_timer(); + } + + fn stop_timers(&mut self) { + self.reconnect_timer = None; + self.retransmit_timer = None; + } + + fn start_reconnect_timer(&mut self) { + self.reconnect_timer = + Some(SystemTime::now() + Duration::from_millis(CONNECTION_RETRY_INTERVAL)); + } + + fn start_retransmit_timer(&mut self) { + self.retransmit_timer = + Some(SystemTime::now() + Duration::from_millis(RETRANSMIT_CHECK_INTERVAL)); + } +} diff --git a/atem-connection-rs/src/atem_lib/atem_util.rs b/atem-connection-rs/src/atem_lib/atem_util.rs new file mode 100644 index 0000000..09b970a --- /dev/null +++ b/atem-connection-rs/src/atem_lib/atem_util.rs @@ -0,0 +1,4 @@ +pub const COMMAND_CONNECT_HELLO: [u8; 20] = [ + 0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +]; diff --git a/atem-connection-rs/src/atem_lib/mod.rs b/atem-connection-rs/src/atem_lib/mod.rs new file mode 100644 index 0000000..6a8af6a --- /dev/null +++ b/atem-connection-rs/src/atem_lib/mod.rs @@ -0,0 +1,4 @@ +mod atem_packet; +pub mod atem_socket; +mod atem_socket_inner; +pub mod atem_util; diff --git a/atem-connection-rs/src/commands/command_base.rs b/atem-connection-rs/src/commands/command_base.rs new file mode 100644 index 0000000..7ebecaf --- /dev/null +++ b/atem-connection-rs/src/commands/command_base.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; + +use crate::{enums::ProtocolVersion, state::AtemState}; + +pub trait IDeserializedCommand { + fn apply_to_state(&self, state: &mut AtemState) -> Vec; +} + +pub trait DeserializedCommand: IDeserializedCommand { + fn get_raw_name(&self) -> Option; + fn get_minimum_version(&self) -> Option; +} + +pub trait ISerializableCommand { + fn serialize(version: ProtocolVersion) -> Vec; +} + +pub trait BasicWritableCommand: ISerializableCommand { + fn get_raw_name(&self) -> Option; + fn get_minimum_version(&self) -> Option; +} + +pub trait WritableCommand: BasicWritableCommand { + fn get_mask_flag(&self) -> HashMap; + fn get_flag(&self) -> f64; + fn set_flag(&mut self, flag: f64); +} + +pub trait SymmetricalCommand: DeserializedCommand + ISerializableCommand {} diff --git a/atem-connection-rs/src/commands/mod.rs b/atem-connection-rs/src/commands/mod.rs new file mode 100644 index 0000000..0ddc932 --- /dev/null +++ b/atem-connection-rs/src/commands/mod.rs @@ -0,0 +1 @@ +pub mod command_base; diff --git a/atem-connection-rs/src/enums/mod.rs b/atem-connection-rs/src/enums/mod.rs new file mode 100644 index 0000000..8a2be2f --- /dev/null +++ b/atem-connection-rs/src/enums/mod.rs @@ -0,0 +1,410 @@ +pub enum Model { + Unknown = 0x00, + TVS = 0x01, + OneME = 0x02, + TwoME = 0x03, + PS4K = 0x04, + OneME4K = 0x05, + TwoME4K = 0x06, + TwoMEBS4K = 0x07, + TVSHD = 0x08, + TVSProHD = 0x09, + TVSPro4K = 0x0a, + Constellation = 0x0b, + Constellation8K = 0x0c, + Mini = 0x0d, + MiniPro = 0x0e, + MiniProISO = 0x0f, + MiniExtreme = 0x10, + MiniExtremeISO = 0x11, +} + +impl Default for Model { + fn default() -> Self { + Model::Unknown + } +} + +pub enum ProtocolVersion { + Unknown = 0, + V7_2 = 0x00020016, // 2.22 // TODO - verify this is correct + V7_5_2 = 0x0002001b, // 2.27 // The naming of this may be off + V8_0 = 0x0002001c, // 2.28 + V8_0_1 = 0x0002001d, // 2.29 + V8_1_1 = 0x0002001e, // 2.30 +} + +impl Default for ProtocolVersion { + fn default() -> Self { + ProtocolVersion::Unknown + } +} + +pub enum TransitionStyle { + MIX = 0x00, + DIP = 0x01, + WIPE = 0x02, + DVE = 0x03, + STING = 0x04, +} + +pub enum TransitionSelection { + Background = 1 << 0, + Key1 = 1 << 1, + Key2 = 1 << 2, + Key3 = 1 << 3, + Key4 = 1 << 4, +} + +pub enum DVEEffect { + SwooshTopLeft = 0, + SwooshTop = 1, + SwooshTopRight = 2, + SwooshLeft = 3, + SwooshRight = 4, + SwooshBottomLeft = 5, + SwooshBottom = 6, + SwooshBottomRight = 7, + + SpinCCWTopRight = 13, + SpinCWTopLeft = 8, + SpinCCWBottomRight = 15, + SpinCWBottomLeft = 10, + SpinCWTopRight = 9, + SpinCCWTopLeft = 12, + SpinCWBottomRight = 11, + SpinCCWBottomLeft = 14, + + SqueezeTopLeft = 16, + SqueezeTop = 17, + SqueezeTopRight = 18, + SqueezeLeft = 19, + SqueezeRight = 20, + SqueezeBottomLeft = 21, + SqueezeBottom = 22, + SqueezeBottomRight = 23, + + PushTopLeft = 24, + PushTop = 25, + PushTopRight = 26, + PushLeft = 27, + PushRight = 28, + PushBottomLeft = 29, + PushBottom = 30, + PushBottomRight = 31, + + GraphicCWSpin = 32, + GraphicCCWSpin = 33, + GraphicLogoWipe = 34, +} + +pub enum MacroAction { + Run = 0, + Stop = 1, + StopRecord = 2, + InsertUserWait = 3, + Continue = 4, + Delete = 5, +} + +pub enum ExternalPortType { + Unknown = 0, + SDI = 1, + HDMI = 2, + Component = 4, + Composite = 8, + SVideo = 16, + XLR = 32, + AESEBU = 64, + RCA = 128, + Internal = 256, + TSJack = 512, + MADI = 1024, + TRSJack = 2048, +} + +pub enum InternalPortType { + External = 0, + Black = 1, + ColorBars = 2, + ColorGenerator = 3, + MediaPlayerFill = 4, + MediaPlayerKey = 5, + SuperSource = 6, + // Since V8_1_1 + ExternalDirect = 7, + + MEOutput = 128, + Auxiliary = 129, + Mask = 130, + // Since V8_1_1 + MultiViewer = 131, +} + +const SOURCE_AVAILABILITY_NONE: isize = 0; +const SOURCE_AVAILABILITY_AUXILIARY: isize = 1 << 0; +const SOURCE_AVAILABILITY_MULTIVIEWER: isize = 1 << 1; +const SOURCE_AVAILABILITY_SUPERSOURCE_ART: isize = 1 << 2; +const SOURCE_AVAILABILITY_SUPERSOURCE_BOX: isize = 1 << 3; +const SOURCE_AVAILABILITY_KEY_SOURCE: isize = 1 << 4; +const SOURCE_AVAILABILITY_AUXILIARY_1: isize = 1 << 5; +const SOURCE_AVAILABILITY_AUXILIARY_2: isize = 1 << 6; +pub enum SourceAvailability { + None = SOURCE_AVAILABILITY_NONE, + Auxiliary = SOURCE_AVAILABILITY_AUXILIARY, + Multiviewer = SOURCE_AVAILABILITY_MULTIVIEWER, + SuperSourceArt = SOURCE_AVAILABILITY_SUPERSOURCE_ART, + SuperSourceBox = SOURCE_AVAILABILITY_SUPERSOURCE_BOX, + KeySource = SOURCE_AVAILABILITY_KEY_SOURCE, + Auxiliary1 = SOURCE_AVAILABILITY_AUXILIARY_1, + Auxiliary2 = SOURCE_AVAILABILITY_AUXILIARY_2, + All = SOURCE_AVAILABILITY_AUXILIARY + | SOURCE_AVAILABILITY_MULTIVIEWER + | SOURCE_AVAILABILITY_SUPERSOURCE_ART + | SOURCE_AVAILABILITY_SUPERSOURCE_BOX + | SOURCE_AVAILABILITY_KEY_SOURCE + | SOURCE_AVAILABILITY_AUXILIARY_1 + | SOURCE_AVAILABILITY_AUXILIARY_2, // Auxiliary | Multiviewer | SuperSourceArt | SuperSourceBox | KeySource | Auxiliary1 | Auxiliary2 +} + +const ME_AVAILABILITY_NONE: isize = 0; +const ME_AVAILABILITY_ME_1: isize = 1 << 0; +const ME_AVAILABILITY_ME_2: isize = 1 << 1; +const ME_AVAILABILITY_ME_3: isize = 1 << 2; +const ME_AVAILABILITY_ME_4: isize = 1 << 3; +pub enum MeAvailability { + None = ME_AVAILABILITY_NONE, + Me1 = ME_AVAILABILITY_ME_1, + Me2 = ME_AVAILABILITY_ME_2, + Me3 = ME_AVAILABILITY_ME_3, + Me4 = ME_AVAILABILITY_ME_4, + All = ME_AVAILABILITY_ME_1 | ME_AVAILABILITY_ME_2 | ME_AVAILABILITY_ME_3 | ME_AVAILABILITY_ME_4, +} + +pub enum BorderBevel { + None = 0, + InOut = 1, + In = 2, + Out = 3, +} + +pub enum IsAtKeyFrame { + None = 0, + A = 1 << 0, + B = 1 << 1, + RunToInfinite = 1 << 2, +} + +pub enum Pattern { + LeftToRightBar = 0, + TopToBottomBar = 1, + HorizontalBarnDoor = 2, + VerticalBarnDoor = 3, + CornersInFourBox = 4, + RectangleIris = 5, + DiamondIris = 6, + CircleIris = 7, + TopLeftBox = 8, + TopRightBox = 9, + BottomRightBox = 10, + BottomLeftBox = 11, + TopCentreBox = 12, + RightCentreBox = 13, + BottomCentreBox = 14, + LeftCentreBox = 15, + TopLeftDiagonal = 16, + TopRightDiagonal = 17, +} + +#[derive(Clone, Copy)] +pub enum MixEffectKeyType { + Luma = 0, + Chroma = 1, + Pattern = 2, + DVE = 3, +} + +pub enum FlyKeyKeyFrame { + None = 0, + A = 1, + B = 2, + Full = 3, + RunToInfinite = 4, +} + +pub enum FlyKeyDirection { + CentreOfKey = 0, + TopLeft = 1, + TopCentre = 2, + TopRight = 3, + MiddleLeft = 4, + MiddleCentre = 5, + MiddleRight = 6, + BottomLeft = 7, + BottomCentre = 8, + BottomRight = 9, +} + +pub enum SuperSourceArtOption { + Background, + Foreground, +} + +pub enum TransferMode { + NoOp, + Write, + Clear, + WriteAudio = 256, +} + +pub enum VideoMode { + N525i5994NTSC = 0, + P625i50PAL = 1, + N525i5994169 = 2, + P625i50169 = 3, + + P720p50 = 4, + N720p5994 = 5, + P1080i50 = 6, + N1080i5994 = 7, + N1080p2398 = 8, + N1080p24 = 9, + P1080p25 = 10, + N1080p2997 = 11, + P1080p50 = 12, + N1080p5994 = 13, + + N4KHDp2398 = 14, + N4KHDp24 = 15, + P4KHDp25 = 16, + N4KHDp2997 = 17, + + P4KHDp5000 = 18, + N4KHDp5994 = 19, + + N8KHDp2398 = 20, + N8KHDp24 = 21, + P8KHDp25 = 22, + N8KHDp2997 = 23, + P8KHDp50 = 24, + N8KHDp5994 = 25, + + N1080p30 = 26, + N1080p60 = 27, +} + +impl Default for VideoMode { + fn default() -> Self { + VideoMode::N525i5994NTSC + } +} + +pub enum TransferState { + Queued, + Locked, + Transferring, + Finished, +} + +pub enum MediaSourceType { + Still = 1, + Clip, +} + +pub enum AudioMixOption { + Off = 0, + On = 1, + AudioFollowVideo = 2, +} + +pub enum AudioSourceType { + ExternalVideo, + MediaPlayer, + ExternalAudio, +} + +pub enum StreamingError { + None, + InvalidState = 1 << 4, + Unknown = 1 << 15, +} + +pub enum StreamingStatus { + Idle = 1 << 0, + Connecting = 1 << 1, + Streaming = 1 << 2, + Stopping = 1 << 5, // + Streaming +} + +pub enum RecordingError { + None = 1 << 1, + NoMedia = 0, + MediaFull = 1 << 2, + MediaError = 1 << 3, + MediaUnformatted = 1 << 4, + DroppingFrames = 1 << 5, + Unknown = 1 << 15, +} + +pub enum RecordingStatus { + Idle = 0, + Recording = 1 << 0, + Stopping = 1 << 7, +} + +pub enum RecordingDiskStatus { + Idle = 1 << 0, + Unformatted = 1 << 1, + Active = 1 << 2, + Recording = 1 << 3, + + Removed = 1 << 5, +} + +pub enum FairlightAudioMixOption { + Off = 1, + On = 2, + AudioFollowVideo = 4, +} + +pub enum FairlightInputConfiguration { + Mono = 1, + Stereo = 2, + DualMono = 4, +} + +pub enum FairlightAnalogInputLevel { + Microphone = 1, + ConsumerLine = 2, + // [Since(ProtocolVersion.V8_1_1)] + ProLine = 4, +} + +pub enum FairlightAudioSourceType { + Mono = 0, + Stereo = 1, +} + +pub enum FairlightInputType { + EmbeddedWithVideo = 0, + MediaPlayer = 1, + AudioIn = 2, + MADI = 4, +} + +const MULTI_VIEWER_LAYOUT_DEFAULT: isize = 0; +const MULTI_VIEWER_LAYOUT_TOP_LEFT_SMALL: isize = 1; +const MULTI_VIEWER_LAYOUT_TOP_RIGHT_SMALL: isize = 2; +const MULTI_VIEWER_LAYOUT_BOTTOM_LEFT_SMALL: isize = 4; +const MULTI_VIEWER_LAYOUT_BOTTOM_RIGHT_SMALL: isize = 8; +pub enum MultiViewerLayout { + Default = MULTI_VIEWER_LAYOUT_DEFAULT, + TopLeftSmall = MULTI_VIEWER_LAYOUT_TOP_LEFT_SMALL, + TopRightSmall = MULTI_VIEWER_LAYOUT_TOP_RIGHT_SMALL, + ProgramBottom = MULTI_VIEWER_LAYOUT_TOP_LEFT_SMALL | MULTI_VIEWER_LAYOUT_TOP_RIGHT_SMALL, // TopLeftSmall | TopRightSmall + BottomLeftSmall = MULTI_VIEWER_LAYOUT_BOTTOM_LEFT_SMALL, + ProgramRight = MULTI_VIEWER_LAYOUT_TOP_LEFT_SMALL | MULTI_VIEWER_LAYOUT_BOTTOM_LEFT_SMALL, // TopLeftSmall | BottomLeftSmall + BottomRightSmall = MULTI_VIEWER_LAYOUT_BOTTOM_RIGHT_SMALL, + ProgramLeft = MULTI_VIEWER_LAYOUT_TOP_RIGHT_SMALL | MULTI_VIEWER_LAYOUT_BOTTOM_RIGHT_SMALL, // TopRightSmall | BottomRightSmall + ProgramTop = MULTI_VIEWER_LAYOUT_BOTTOM_LEFT_SMALL | MULTI_VIEWER_LAYOUT_BOTTOM_RIGHT_SMALL, // BottomLeftSmall | BottomRightSmall +} diff --git a/atem-connection-rs/src/lib.rs b/atem-connection-rs/src/lib.rs new file mode 100644 index 0000000..f0a8e48 --- /dev/null +++ b/atem-connection-rs/src/lib.rs @@ -0,0 +1,13 @@ +#[macro_use] +extern crate derive_new; +#[macro_use] +extern crate derive_getters; +extern crate tokio; +#[macro_use] +extern crate thiserror; + +pub mod atem; +pub mod atem_lib; +pub mod commands; +pub mod enums; +pub mod state; diff --git a/atem-connection-rs/src/state/atem_macro.rs b/atem-connection-rs/src/state/atem_macro.rs new file mode 100644 index 0000000..5bbc8f9 --- /dev/null +++ b/atem-connection-rs/src/state/atem_macro.rs @@ -0,0 +1,28 @@ +#[derive(Getters, new, Default)] +pub struct MacroPlayerState { + pub is_running: bool, + pub is_waiting: bool, + pub is_loop: bool, + pub macro_index: u64, +} + +#[derive(Getters, new, Default)] +pub struct MacroRecorderState { + pub is_recording: bool, + pub macro_index: u64, +} + +#[derive(Getters, new)] +pub struct MacroPropertiesState { + is_used: bool, + has_unsupported_ops: bool, + pub name: String, + pub description: String, +} + +#[derive(Getters, new, Default)] +pub struct MacroState { + pub macro_player: MacroPlayerState, + pub macro_recorder: MacroRecorderState, + macro_properties: Vec, +} diff --git a/atem-connection-rs/src/state/audio.rs b/atem-connection-rs/src/state/audio.rs new file mode 100644 index 0000000..0e478a7 --- /dev/null +++ b/atem-connection-rs/src/state/audio.rs @@ -0,0 +1,57 @@ +use std::collections::HashMap; + +use crate::enums::{AudioMixOption, AudioSourceType, ExternalPortType}; + +pub type AudioChannel = ClassicAudioChannel; +pub type AudioMasterChannel = ClassicAudioMasterChannel; +pub type AtemAudioState = AtemClassicAudioState; + +#[derive(Getters, new)] +pub struct ClassicAudioChannel { + source_type: AudioSourceType, + pub port_type: ExternalPortType, + pub mix_option: AudioMixOption, + pub gain: f64, + pub balance: f64, + + supports_rca_to_xlr_enabled: bool, + pub rca_to_xlr_enabled: bool, +} + +#[derive(Getters, new)] +pub struct ClassicAudioMasterChannel { + pub gain: f64, + pub balance: f64, + pub follow_fade_to_black: bool, +} + +#[derive(Getters, new)] +pub struct ClassicAudioMonitorChannel { + pub enabled: bool, + pub gain: f64, + pub mute: bool, + pub solo: bool, + pub solo_source: f64, + pub dim: bool, + pub dim_level: f64, +} + +#[derive(Getters, new)] +pub struct ClassicAudioHeadphoneOutputChannel { + pub gain: f64, + pub program_out_gain: f64, + pub sidetone_gain: f64, + pub talkback_gain: f64, +} + +#[derive(Getters, new)] +pub struct AtemClassicAudioState { + number_of_channels: Option, + has_monitor: Option, + pub channels: HashMap, + pub monitor: Option, + pub headphones: Option, + pub master: Option, + + pub audio_follow_video_crossfade_transition_enabled: Option, +} diff --git a/atem-connection-rs/src/state/color.rs b/atem-connection-rs/src/state/color.rs new file mode 100644 index 0000000..4f8acb1 --- /dev/null +++ b/atem-connection-rs/src/state/color.rs @@ -0,0 +1,6 @@ +#[derive(Getters, new)] +pub struct ColorGeneratorState { + pub hue: u64, + pub saturation: u64, + pub luma: u64 +} diff --git a/atem-connection-rs/src/state/common.rs b/atem-connection-rs/src/state/common.rs new file mode 100644 index 0000000..11b2d98 --- /dev/null +++ b/atem-connection-rs/src/state/common.rs @@ -0,0 +1,8 @@ +#[derive(Getters, new)] +pub struct Timecode { + pub hours: u64, + pub minutes: u64, + pub seconds: u64, + pub frames: u64, + pub is_drop_frame: bool, +} diff --git a/atem-connection-rs/src/state/fairlight.rs b/atem-connection-rs/src/state/fairlight.rs new file mode 100644 index 0000000..3bbedfc --- /dev/null +++ b/atem-connection-rs/src/state/fairlight.rs @@ -0,0 +1,141 @@ +use std::collections::HashMap; + +use crate::enums::{ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType, FairlightInputConfiguration, FairlightInputType}; + +#[derive(Getters, new)] +pub struct FairlightAudioDynamicsState { + pub make_up_gain: Option, + + pub limiter: Option, + pub compressor: Option, + pub expander: Option +} + +#[derive(Getters, new)] +pub struct FairlightAudioLimiterState { + pub limiter_enabled: bool, + pub threshold: u64, + pub attack: u64, + pub hold: u64, + pub release: u64 +} + +#[derive(Getters, new)] +pub struct FairlightAudioCompressorState { + pub compressor_enabled: bool, + pub threshold: u64, + pub ratio: u64, + pub attack: u64, + pub hold: u64, + pub release: u64 +} + +#[derive(Getters, new)] +pub struct FairlightAudioExpanderState { + pub expander_enabled: bool, + pub gate_enabled: bool, + pub threshold: u64, + pub range: u64, + pub ratio: u64, + pub attack: u64, + pub hold: u64, + pub release: u64 +} + +#[derive(Getters, new)] +pub struct FairlightAudioEqualizerBandState { + pub band_enabled: bool, + + supported_shapes: Vec, // TODO + pub shape: u64, // TODO + + supported_frequency_ranges: Vec, // TODO + pub frequency_ranges: u64, // TODO + + pub frequency: u64, + + pub gain: u64, + pub q_factor: u64 +} + +#[derive(Getters, new)] +pub struct FairlightAudioMasterChannelPropertiesState { + // Gain in decibel, -Infinity to +6dB + pub fader_gain: u64, + pub follow_fade_to_black: bool +} + +#[derive(Getters, new)] +pub struct FairlightAudioMasterChannel { + pub properties: Option, + + pub equalizer: Option, + pub dynamicss: Option, +} + +#[derive(Getters, new)] +pub struct FairlightAudioMonitorChannel { + pub gain: u64, + pub input_master_gain: u64, + pub input_talkback_gain: u64, + pub input_sidetone_gain: u64 +} + +#[derive(Getters, new)] +pub struct FairlightAudioSource { + pub properties: Option, + pub equalizer: Option, + pub dynamics: Option +} + +#[derive(Getters, new)] +pub struct FairlightAudioEqualizerState { + pub enabled: bool, + pub gain: u64, + bands: Vec +} + +#[derive(Getters, new)] +pub struct FairlightAudioSourcePropertiesState { + source_type: FairlightAudioSourceType, + + max_frames_delay: u64, + pub frames_delay: u64, + + has_stereo_simulation: bool, + pub stereo_simulation: u64, + + pub gain: u64, + pub balance: u64, + pub fader_gain: u64, + + supported_mix_options: Vec, + pub mix_option: FairlightAudioMixOption +} + +#[derive(Getters, new)] +pub struct FairlightAudioInput { + pub properties: Option, + pub sources: HashMap +} + +#[derive(Getters, new)] +pub struct FairlightAudioInputProperties { + input_type: FairlightInputType, + external_port_type: ExternalPortType, + + supported_configurations: Vec, + pub active_configuration: FairlightInputConfiguration, + + supported_input_levels: Vec, + pub activeInputLevel: FairlightAnalogInputLevel +} + +#[derive(Getters, new)] +pub struct AtemFairlightAudioState { + pub inputs: HashMap, + pub master: Option, + pub monitor: Option, + + pub audio_follow_video_crossfade_transition_enabled: Option +} diff --git a/atem-connection-rs/src/state/info.rs b/atem-connection-rs/src/state/info.rs new file mode 100644 index 0000000..4620c73 --- /dev/null +++ b/atem-connection-rs/src/state/info.rs @@ -0,0 +1,86 @@ +use crate::enums::{Model, ProtocolVersion}; + +#[derive(Getters, new)] +pub struct AtemCapabilites { + mix_effects: u64, + sources: u64, + auxilliaries: u64, + mix_minus_outputs: u64, + media_players: u64, + serial_ports: u64, + max_hyperdecks: u64, + dves: u64, + stingers: u64, + super_sources: u64, + talkback_channels: u64, + downstream_keyers: u64, + camera_control: bool, + advanced_chroma_keyers: bool, + only_configurable_outputs: bool, +} + +#[derive(Getters, new)] +pub struct MixEffectInfo { + key_count: u64, +} + +#[derive(Getters, new)] +pub struct SuperSourceInfo { + box_count: u64, +} + +#[derive(Getters, new)] +pub struct AudioMixerInfo { + inputs: u64, + monitors: u64, + headphones: u64, +} + +#[derive(Getters, new)] +pub struct FairlightAudioMixerInfo { + inputs: u64, + monitors: u64, +} + +#[derive(Getters, new)] +pub struct MacroPoolInfo { + macro_count: u64, +} + +#[derive(Getters, new)] +pub struct MediaPoolInfo { + still_count: u64, + clip_count: u64, +} + +#[derive(Getters, new)] +pub struct MultiviewerInfo { + count: u64, + window_count: u64, +} + +#[derive(new)] +pub struct TimeInfo { + pub hour: u64, + pub minute: u64, + pub second: u64, + pub frame: u64, + pub drop_frame: bool, +} + +#[derive(new, Default)] +pub struct DeviceInfo { + pub api_version: ProtocolVersion, + pub capabilities: Option, + pub model: Model, + pub product_identifier: Option, + pub super_sources: Vec>, + pub mix_effects: Vec>, + pub power: Vec, + pub audio_mixer: Option, + pub fairlight_mixer: Option, + pub macro_pool: Option, + pub media_pool: Option, + pub multiviewer: Option, + pub lastTime: Option, +} diff --git a/atem-connection-rs/src/state/input.rs b/atem-connection-rs/src/state/input.rs new file mode 100644 index 0000000..5fd2f0b --- /dev/null +++ b/atem-connection-rs/src/state/input.rs @@ -0,0 +1,14 @@ +use crate::enums::{ExternalPortType, InternalPortType, MeAvailability, SourceAvailability}; + +#[derive(Getters, new)] +pub struct InputChannel { + input_id: u64, + pub long_name: String, + pub short_name: String, + are_names_default: bool, + external_ports: Vec, + pub external_port_type: ExternalPortType, + internal_port_type: InternalPortType, + source_availability: SourceAvailability, + me_availability: MeAvailability +} diff --git a/atem-connection-rs/src/state/media.rs b/atem-connection-rs/src/state/media.rs new file mode 100644 index 0000000..f71788f --- /dev/null +++ b/atem-connection-rs/src/state/media.rs @@ -0,0 +1,40 @@ +use crate::enums; + +#[derive(Getters, new)] +pub struct MediaPlayer { + pub playing: bool, + pub is_loop: bool, + pub at_beginning: bool, + pub clip_frame: u64, +} + +#[derive(Getters, new)] +pub struct MediaPlayerSource { + pub source_type: enums::MediaSourceType, + pub clip_index: u64, + pub still_index: u64, +} + +pub type MediaPlayerState = (MediaPlayer, MediaPlayerSource); + +#[derive(Getters, new, Default)] +pub struct MediaState { + still_pool: Vec, + clip_pool: Vec, + players: Vec, +} + +#[derive(Getters, new)] +pub struct StillFrame { + pub is_used: bool, + pub hash: String, + pub file_name: String, +} + +#[derive(Getters, new)] +pub struct ClipBank { + pub is_used: bool, + pub name: String, + pub frame_count: u64, + pub frames: Vec, +} diff --git a/atem-connection-rs/src/state/mod.rs b/atem-connection-rs/src/state/mod.rs new file mode 100644 index 0000000..ea46c34 --- /dev/null +++ b/atem-connection-rs/src/state/mod.rs @@ -0,0 +1,31 @@ +use std::collections::HashMap; + +pub mod atem_macro; +pub mod audio; +pub mod color; +pub mod common; +pub mod fairlight; +pub mod info; +pub mod input; +pub mod media; +pub mod recording; +pub mod settings; +pub mod streaming; +pub mod util; +pub mod video; + +#[derive(Default)] +pub struct AtemState { + info: info::DeviceInfo, + video: video::AtemVideoState, + audio: Option, + fairlight: Option, + media: media::MediaState, + inputs: HashMap, + // macro is a rust keyword + atem_macro: atem_macro::MacroState, + settings: settings::SettingsState, + recording: Option, + streaming: Option, + color_generators: HashMap, +} diff --git a/atem-connection-rs/src/state/recording.rs b/atem-connection-rs/src/state/recording.rs new file mode 100644 index 0000000..3cafa4b --- /dev/null +++ b/atem-connection-rs/src/state/recording.rs @@ -0,0 +1,41 @@ +use std::collections::HashMap; + +use crate::enums::{RecordingDiskStatus, RecordingError, RecordingStatus}; + +use super::common::Timecode; + +#[derive(Getters, new)] +pub struct RecordingState { + pub status: Option, + pub properties: RecordingStateProperties, + + pub duration: Option, + + pub disks: HashMap +} + +#[derive(Getters, new)] +pub struct RecordingDiskProperties { + pub disk_id: u64, + pub volume_name: String, + pub recording_time_available: u64, + pub status: RecordingDiskStatus +} + +#[derive(Getters, new)] +pub struct RecordingStateStatus { + pub state: RecordingStatus, + pub error: RecordingError, + + pub recording_time_available: u64 +} + +#[derive(Getters, new)] +pub struct RecordingStateProperties { + pub filename: String, + + pub working_set_1_disk_id: u64, + pub working_set_2_disk_id: u64, + + pub record_in_all_cameras: bool +} diff --git a/atem-connection-rs/src/state/settings.rs b/atem-connection-rs/src/state/settings.rs new file mode 100644 index 0000000..ae4450c --- /dev/null +++ b/atem-connection-rs/src/state/settings.rs @@ -0,0 +1,61 @@ +use crate::enums::{MultiViewerLayout, VideoMode}; + +pub trait MultiViewerSourceState { + fn get_source(&self) -> u64; + fn set_source(&mut self, source: u64); + + fn get_window_index(&self) -> u64; + fn get_supports_vu_meter(&self) -> bool; + fn get_supports_safe_area(&self) -> bool; +} + +#[derive(Getters, new)] +pub struct MultiViewerWindowState { + pub safe_title: Option, + pub audio_meter: Option, + + // SourceState props + pub source: u64, + window_index: u64, + supports_vu_meter: bool, + supports_safe_area: bool, +} + +impl MultiViewerSourceState for MultiViewerWindowState { + fn get_source(&self) -> u64 { + self.source + } + fn set_source(&mut self, source: u64) { + self.source = source; + } + + fn get_window_index(&self) -> u64 { + self.window_index + } + fn get_supports_vu_meter(&self) -> bool { + self.supports_vu_meter + } + fn get_supports_safe_area(&self) -> bool { + self.supports_safe_area + } +} + +#[derive(Getters, new)] +pub struct MultiViewerPropertiesState { + pub layout: MultiViewerLayout, + pub program_preview_swapped: bool, +} + +#[derive(Getters, new)] +pub struct MultiViewer { + index: u64, + windows: Vec, + pub properties: Option, + pub vu_opacity: Option, +} + +#[derive(Getters, new, Default)] +pub struct SettingsState { + multi_viewers: Vec, + pub video_mode: VideoMode, +} diff --git a/atem-connection-rs/src/state/streaming.rs b/atem-connection-rs/src/state/streaming.rs new file mode 100644 index 0000000..5608ec1 --- /dev/null +++ b/atem-connection-rs/src/state/streaming.rs @@ -0,0 +1,32 @@ +use crate::enums::{StreamingError, StreamingStatus}; + +use super::common::Timecode; + +#[derive(Getters)] +pub struct StreamingState { + pub status: Option, + pub stats: Option, + pub service: StreamingServiceProperties, + + pub duration: Option +} + +#[derive(Getters, new)] +pub struct StreamingStateStatus { + state: StreamingStatus, + error: StreamingError +} + +#[derive(Getters, new)] +pub struct StreamingStateStats { + cache_used: u64, + encoding_bitrate: u64 +} + +pub struct StreamingServiceProperties { + pub service_name: String, + pub url: String, + pub key: String, + + bitrates: (u64, u64) +} diff --git a/atem-connection-rs/src/state/util.rs b/atem-connection-rs/src/state/util.rs new file mode 100644 index 0000000..914821d --- /dev/null +++ b/atem-connection-rs/src/state/util.rs @@ -0,0 +1,9 @@ +use super::{settings::MultiViewer, AtemState}; + +pub fn create() -> AtemState { + AtemState::default() +} + +pub fn get_multi_viewer(state: &mut AtemState, index: usize) -> Option<&MultiViewer> { + state.settings.multi_viewers().get(index) +} diff --git a/atem-connection-rs/src/state/video/downstream_keyers.rs b/atem-connection-rs/src/state/video/downstream_keyers.rs new file mode 100644 index 0000000..0eb9433 --- /dev/null +++ b/atem-connection-rs/src/state/video/downstream_keyers.rs @@ -0,0 +1,111 @@ +pub trait DownstreamKeyerBase { + fn get_in_transition(&self) -> bool; + fn get_remaining_frames(&self) -> f64; + fn get_is_auto(&self) -> bool; + + fn get_on_air(&self) -> bool; + fn set_on_air(&mut self, on_air: bool); + fn get_is_towards_on_air(&self) -> Option; + fn set_is_towards_on_air(&mut self, on_air: Option); +} + +#[derive(Getters, new)] +pub struct DownstreamKeyer { + pub sources: Option, + pub properties: Option, + + in_transition: bool, + remaining_frames: f64, + is_auto: bool, + pub on_air: bool, + pub is_towards_air: Option, +} + +impl DownstreamKeyerBase for DownstreamKeyer { + fn get_in_transition(&self) -> bool { + self.in_transition + } + fn get_remaining_frames(&self) -> f64 { + self.remaining_frames + } + fn get_is_auto(&self) -> bool { + self.is_auto + } + + fn get_on_air(&self) -> bool { + self.on_air + } + fn set_on_air(&mut self, on_air: bool) { + self.on_air = on_air + } + fn get_is_towards_on_air(&self) -> Option { + self.is_towards_air + } + fn set_is_towards_on_air(&mut self, on_air: Option) { + self.is_towards_air = on_air + } +} + +pub trait DownstreamKeyerGeneral { + fn get_pre_multiply(&self) -> bool; + fn set_pre_multiply(&mut self, pre_multiply: bool); + fn get_clip(&self) -> f64; + fn set_clip(&mut self, clip: f64); + fn get_gain(&self) -> f64; + fn set_gain(&mut self, gain: f64); + fn get_invert(&self) -> bool; + fn set_invert(&mut self, invert: bool); +} + +#[derive(Getters, new)] +pub struct DownstreamKeyerMask { + pub enabled: bool, + pub top: f64, + pub bottom: f64, + pub left: f64, + pub right: f64, +} + +#[derive(Getters, new)] +pub struct DownstreamKeyerProperties { + pub tie: bool, + pub rate: f64, + pub mask: DownstreamKeyerMask, + pub pre_multiply: bool, + pub clip: f64, + pub gain: f64, + pub invert: bool, +} + +impl DownstreamKeyerGeneral for DownstreamKeyerProperties { + fn get_pre_multiply(&self) -> bool { + self.pre_multiply + } + fn set_pre_multiply(&mut self, pre_multiply: bool) { + self.pre_multiply = pre_multiply + } + fn get_clip(&self) -> f64 { + self.clip + } + fn set_clip(&mut self, clip: f64) { + self.clip = clip + } + fn get_gain(&self) -> f64 { + self.gain + } + fn set_gain(&mut self, gain: f64) { + self.gain = gain + } + fn get_invert(&self) -> bool { + self.invert + } + fn set_invert(&mut self, invert: bool) { + self.invert = invert + } +} + +#[derive(Getters, new)] +pub struct DownstreamKeyerSources { + pub fill_source: f64, + pub cut_source: f64, +} diff --git a/atem-connection-rs/src/state/video/mod.rs b/atem-connection-rs/src/state/video/mod.rs new file mode 100644 index 0000000..57fb684 --- /dev/null +++ b/atem-connection-rs/src/state/video/mod.rs @@ -0,0 +1,115 @@ +use crate::enums; + +mod downstream_keyers; +mod super_source; +mod upstream_keyers; + +#[derive(Getters, new)] +pub struct DipTransitionSettings { + pub rate: f64, + pub input: f64, +} + +#[derive(Getters, new)] +pub struct DVETransitionSettings { + pub rate: f64, + pub logo_rate: f64, + pub style: enums::DVEEffect, + pub fill_source: f64, + pub key_source: f64, + + pub enable_key: f64, + pub pre_multiplied: bool, + pub clip: f64, + pub gain: f64, + pub invert_key: bool, + pub reverse: bool, + pub flip_flop: bool, +} + +#[derive(Getters, new)] +pub struct MixTransitionSettings { + pub rate: f64, +} + +#[derive(Getters, new)] +pub struct StingerTransitionSettings { + pub source: f64, + pub pre_multiplied_key: bool, + + pub clip: f64, + pub gain: f64, + pub invert: bool, + + pub preroll: f64, + pub clip_duration: f64, + pub trigger_point: f64, + pub mix_rate: f64, +} + +#[derive(Getters, new)] +pub struct WipeTransitionSettings { + pub rate: f64, + pub pattern: f64, + pub border_width: f64, + pub border_input: f64, + pub symmetry: f64, + pub border_softenss: f64, + pub x_position: f64, + pub y_position: f64, + pub reverse_direction: bool, + pub flip_flop: bool, +} + +#[derive(Getters, new)] +pub struct TransitionProperties { + style: enums::TransitionStyle, + selection: Vec, + pub next_style: enums::TransitionStyle, + pub next_selection: Vec, +} + +#[derive(Getters, new)] +pub struct TransitionSettings { + pub dip: Option, + pub dve: Option, + pub mix: Option, + pub stinger: Option, + pub wipe: Option, +} + +#[derive(Getters, new)] +pub struct TransitionPosition { + in_transition: bool, + remaining_frames: f64, + pub handle_position: f64, +} + +#[derive(Getters, new)] +pub struct MixEffect { + index: f64, + pub program_input: f64, + pub preview_input: f64, + pub transition_preview: bool, + pub fade_to_black: Option, + pub transition_position: TransitionPosition, + pub transition_properties: TransitionProperties, + pub transition_settings: TransitionSettings, + upstream_keyers: Vec, +} + +#[derive(Getters, new)] +pub struct FadeToBlackProperties { + is_fully_black: bool, + in_transition: bool, + remaining_frames: f64, + pub rate: f64, +} + +#[derive(Getters, new, Default)] +pub struct AtemVideoState { + mix_effects: Vec, + downstream_keyers: Vec, + auxiliaries: Vec, + super_sources: Vec, +} diff --git a/atem-connection-rs/src/state/video/super_source.rs b/atem-connection-rs/src/state/video/super_source.rs new file mode 100644 index 0000000..400cb2b --- /dev/null +++ b/atem-connection-rs/src/state/video/super_source.rs @@ -0,0 +1,51 @@ +use crate::enums; + +#[derive(Getters, new)] +pub struct SuperSourceBox { + pub enabled: bool, + pub source: f64, + pub x: f64, + pub y: f64, + pub size: f64, + pub cropped: bool, + pub crop_top: f64, + pub crop_bottom: f64, + pub crop_left: f64, + pub crop_right: f64, +} + +#[derive(Getters, new)] +pub struct SuperSourceProperties { + pub art_fill_source: f64, + pub art_cut_source: f64, + pub art_option: enums::SuperSourceArtOption, + pub art_pre_multiplied: bool, + pub art_clip: f64, + pub art_gain: f64, + pub art_invert_key: bool, +} + +#[derive(Getters, new)] +pub struct SuperSourceBorder { + pub border_enabled: bool, + pub border_bevel: enums::BorderBevel, + pub border_outer_width: f64, + pub border_inner_width: f64, + pub border_outer_softness: f64, + pub border_inner_softness: f64, + pub border_bevel_softness: f64, + pub border_bevel_position: f64, + pub border_hue: f64, + pub border_saturation: f64, + pub border_luma: f64, + pub border_light_source_direction: f64, + pub border_light_source_altitude: f64, +} + +#[derive(Getters, new)] +pub struct SuperSource { + index: f64, + boxes: [Option; 4], + pub properties: Option, + pub border: Option, +} diff --git a/atem-connection-rs/src/state/video/upstream_keyers.rs b/atem-connection-rs/src/state/video/upstream_keyers.rs new file mode 100644 index 0000000..f8c39aa --- /dev/null +++ b/atem-connection-rs/src/state/video/upstream_keyers.rs @@ -0,0 +1,632 @@ +use crate::enums; + +pub trait UpstreamKeyerBase: UpstreamKeyerTypeSettings { + fn get_upstream_keyer_id(&self) -> f64; + fn get_can_fly_key(&self) -> bool; + fn get_fill_source(&self) -> f64; + fn set_fill_source(&mut self, source: f64); + fn get_cut_source(&self) -> f64; + fn set_cut_source(&mut self, source: f64); +} + +pub trait UpstreamKeyerTypeSettings { + fn get_mix_effect_key_type(&self) -> enums::MixEffectKeyType; + fn set_mix_effect_key_type(&mut self, key_type: enums::MixEffectKeyType); + fn get_fly_enabled(&self) -> bool; + fn set_fly_enabled(&mut self, enabled: bool); +} + +pub trait UpstreamKeyerMaskSettings { + fn get_mask_enabled(&self) -> bool; + fn set_mask_enabled(&mut self, enabled: bool); + fn get_mask_top(&self) -> f64; + fn set_mask_top(&mut self, mask: f64); + fn get_mask_bottom(&self) -> f64; + fn set_mask_bottom(&mut self, mask: f64); + fn get_mask_left(&self) -> f64; + fn set_mask_right(&mut self, mask: f64); +} + +pub trait UpstreamKeyerDVEBase: UpstreamKeyerMaskSettings { + fn get_size_x(&self) -> f64; + fn set_size_x(&mut self, size_x: f64); + fn get_size_y(&self) -> f64; + fn set_size_y(&mut self, size_y: f64); + fn get_position_x(&self) -> f64; + fn set_position_x(&mut self, position_x: f64); + fn get_position_y(&self) -> f64; + fn set_position_y(&mut self, position_y: f64); + fn get_rotation(&self) -> f64; + fn set_rotation(&mut self, rotation: f64); + + fn get_border_outer_width(&self) -> f64; + fn set_border_outer_width(&mut self, width: f64); + fn get_border_inner_width(&self) -> f64; + fn set_border_inner_width(&mut self, width: f64); + fn get_border_outer_softness(&self) -> f64; + fn set_border_outer_softness(&mut self, softness: f64); + fn get_border_inner_softness(&self) -> f64; + fn set_border_inner_softness(&mut self, softness: f64); + fn get_border_bevel_softness(&self) -> f64; + fn set_border_bevel_softness(&mut self, softness: f64); + fn get_border_bevel_position(&self) -> f64; + fn set_border_bevel_position(&mut self, position: f64); + + fn get_border_opacity(&self) -> f64; + fn set_border_opacity(&mut self, opacity: f64); + fn get_border_hue(&self) -> f64; + fn set_border_hue(&mut self, hue: f64); + fn get_border_saturation(&self) -> f64; + fn set_border_saturation(&mut self, saturation: f64); + fn get_border_luma(&self) -> f64; + fn set_border_luma(&mut self, luma: f64); + + fn get_light_source_direction(&self) -> f64; + fn set_light_source_direction(&mut self, direction: f64); + fn get_light_source_altitude(&self) -> f64; + fn set_light_source_altitude(&mut self, altitude: f64); +} + +pub struct UpstreamKeyerDVESettings { + pub border_enabled: bool, + pub shadow_enabled: bool, + pub border_bevel: enums::BorderBevel, + pub rate: f64, + + pub mask_enabled: bool, + pub mask_top: f64, + pub mask_bottom: f64, + pub mask_left: f64, + pub mask_right: f64, + + pub size_x: f64, + pub size_y: f64, + pub position_x: f64, + pub position_y: f64, + pub rotation: f64, + + pub border_outer_width: f64, + pub border_inner_width: f64, + pub border_outer_softness: f64, + pub border_inner_softness: f64, + pub border_bevel_softness: f64, + pub border_bevel_position: f64, + + pub border_opacity: f64, + pub border_hue: f64, + pub border_saturation: f64, + pub border_luma: f64, + + pub light_source_direction: f64, + pub light_source_altitude: f64, +} + +impl UpstreamKeyerMaskSettings for UpstreamKeyerDVESettings { + fn get_mask_enabled(&self) -> bool { + self.mask_enabled + } + + fn set_mask_enabled(&mut self, enabled: bool) { + self.mask_enabled = enabled + } + + fn get_mask_top(&self) -> f64 { + self.mask_top + } + + fn set_mask_top(&mut self, mask: f64) { + self.mask_top = mask + } + + fn get_mask_bottom(&self) -> f64 { + self.mask_bottom + } + + fn set_mask_bottom(&mut self, mask: f64) { + self.mask_bottom = mask + } + + fn get_mask_left(&self) -> f64 { + self.mask_left + } + + fn set_mask_right(&mut self, mask: f64) { + self.mask_left = mask + } +} + +impl UpstreamKeyerDVEBase for UpstreamKeyerDVESettings { + fn get_size_x(&self) -> f64 { + self.size_x + } + + fn set_size_x(&mut self, size_x: f64) { + self.size_x = size_x + } + + fn get_size_y(&self) -> f64 { + self.size_y + } + + fn set_size_y(&mut self, size_y: f64) { + self.size_y = size_y + } + + fn get_position_x(&self) -> f64 { + self.position_x + } + + fn set_position_x(&mut self, position_x: f64) { + self.position_x = position_x + } + + fn get_position_y(&self) -> f64 { + self.position_y + } + + fn set_position_y(&mut self, position_y: f64) { + self.position_y = position_y + } + + fn get_rotation(&self) -> f64 { + self.rotation + } + + fn set_rotation(&mut self, rotation: f64) { + self.rotation = rotation + } + + fn get_border_outer_width(&self) -> f64 { + self.border_outer_width + } + + fn set_border_outer_width(&mut self, width: f64) { + self.border_outer_width = width + } + + fn get_border_inner_width(&self) -> f64 { + self.border_inner_width + } + + fn set_border_inner_width(&mut self, width: f64) { + self.border_inner_width = width + } + + fn get_border_outer_softness(&self) -> f64 { + self.border_outer_softness + } + + fn set_border_outer_softness(&mut self, softness: f64) { + self.border_outer_softness = softness + } + + fn get_border_inner_softness(&self) -> f64 { + self.border_inner_softness + } + + fn set_border_inner_softness(&mut self, softness: f64) { + self.border_inner_softness = softness + } + + fn get_border_bevel_softness(&self) -> f64 { + self.border_bevel_softness + } + + fn set_border_bevel_softness(&mut self, softness: f64) { + self.border_bevel_softness = softness + } + + fn get_border_bevel_position(&self) -> f64 { + self.border_bevel_position + } + + fn set_border_bevel_position(&mut self, position: f64) { + self.border_bevel_position = position + } + + fn get_border_opacity(&self) -> f64 { + self.border_opacity + } + + fn set_border_opacity(&mut self, opacity: f64) { + self.border_opacity = opacity + } + + fn get_border_hue(&self) -> f64 { + self.border_hue + } + + fn set_border_hue(&mut self, hue: f64) { + self.border_hue = hue + } + + fn get_border_saturation(&self) -> f64 { + self.border_saturation + } + + fn set_border_saturation(&mut self, saturation: f64) { + self.border_saturation = saturation + } + + fn get_border_luma(&self) -> f64 { + self.border_luma + } + + fn set_border_luma(&mut self, luma: f64) { + self.border_luma = luma + } + + fn get_light_source_direction(&self) -> f64 { + self.light_source_direction + } + + fn set_light_source_direction(&mut self, direction: f64) { + self.light_source_direction = direction + } + + fn get_light_source_altitude(&self) -> f64 { + self.light_source_altitude + } + + fn set_light_source_altitude(&mut self, altitude: f64) { + self.light_source_altitude = altitude + } +} + +pub struct UpstreamKeyerFlyKeyFrame { + key_frame_id: f64, + + pub mask_enabled: bool, + pub mask_top: f64, + pub mask_bottom: f64, + pub mask_left: f64, + pub mask_right: f64, + + pub size_x: f64, + pub size_y: f64, + pub position_x: f64, + pub position_y: f64, + pub rotation: f64, + + pub border_outer_width: f64, + pub border_inner_width: f64, + pub border_outer_softness: f64, + pub border_inner_softness: f64, + pub border_bevel_softness: f64, + pub border_bevel_position: f64, + + pub border_opacity: f64, + pub border_hue: f64, + pub border_saturation: f64, + pub border_luma: f64, + + pub light_source_direction: f64, + pub light_source_altitude: f64, +} + +impl UpstreamKeyerMaskSettings for UpstreamKeyerFlyKeyFrame { + fn get_mask_enabled(&self) -> bool { + self.mask_enabled + } + + fn set_mask_enabled(&mut self, enabled: bool) { + self.mask_enabled = enabled + } + + fn get_mask_top(&self) -> f64 { + self.mask_top + } + + fn set_mask_top(&mut self, mask: f64) { + self.mask_top = mask + } + + fn get_mask_bottom(&self) -> f64 { + self.mask_bottom + } + + fn set_mask_bottom(&mut self, mask: f64) { + self.mask_bottom = mask + } + + fn get_mask_left(&self) -> f64 { + self.mask_left + } + + fn set_mask_right(&mut self, mask: f64) { + self.mask_left = mask + } +} + +impl UpstreamKeyerDVEBase for UpstreamKeyerFlyKeyFrame { + fn get_size_x(&self) -> f64 { + self.size_x + } + + fn set_size_x(&mut self, size_x: f64) { + self.size_x = size_x + } + + fn get_size_y(&self) -> f64 { + self.size_y + } + + fn set_size_y(&mut self, size_y: f64) { + self.size_y = size_y + } + + fn get_position_x(&self) -> f64 { + self.position_x + } + + fn set_position_x(&mut self, position_x: f64) { + self.position_x = position_x + } + + fn get_position_y(&self) -> f64 { + self.position_y + } + + fn set_position_y(&mut self, position_y: f64) { + self.position_y = position_y + } + + fn get_rotation(&self) -> f64 { + self.rotation + } + + fn set_rotation(&mut self, rotation: f64) { + self.rotation = rotation + } + + fn get_border_outer_width(&self) -> f64 { + self.border_outer_width + } + + fn set_border_outer_width(&mut self, width: f64) { + self.border_outer_width = width + } + + fn get_border_inner_width(&self) -> f64 { + self.border_inner_width + } + + fn set_border_inner_width(&mut self, width: f64) { + self.border_inner_width = width + } + + fn get_border_outer_softness(&self) -> f64 { + self.border_outer_softness + } + + fn set_border_outer_softness(&mut self, softness: f64) { + self.border_outer_softness = softness + } + + fn get_border_inner_softness(&self) -> f64 { + self.border_inner_softness + } + + fn set_border_inner_softness(&mut self, softness: f64) { + self.border_inner_softness = softness + } + + fn get_border_bevel_softness(&self) -> f64 { + self.border_bevel_softness + } + + fn set_border_bevel_softness(&mut self, softness: f64) { + self.border_bevel_softness = softness + } + + fn get_border_bevel_position(&self) -> f64 { + self.border_bevel_position + } + + fn set_border_bevel_position(&mut self, position: f64) { + self.border_bevel_position = position + } + + fn get_border_opacity(&self) -> f64 { + self.border_opacity + } + + fn set_border_opacity(&mut self, opacity: f64) { + self.border_opacity = opacity + } + + fn get_border_hue(&self) -> f64 { + self.border_hue + } + + fn set_border_hue(&mut self, hue: f64) { + self.border_hue = hue + } + + fn get_border_saturation(&self) -> f64 { + self.border_saturation + } + + fn set_border_saturation(&mut self, saturation: f64) { + self.border_saturation = saturation + } + + fn get_border_luma(&self) -> f64 { + self.border_luma + } + + fn set_border_luma(&mut self, luma: f64) { + self.border_luma = luma + } + + fn get_light_source_direction(&self) -> f64 { + self.light_source_direction + } + + fn set_light_source_direction(&mut self, direction: f64) { + self.light_source_direction = direction + } + + fn get_light_source_altitude(&self) -> f64 { + self.light_source_altitude + } + + fn set_light_source_altitude(&mut self, altitude: f64) { + self.light_source_altitude = altitude + } +} + +pub struct UpstreamKeyerChromaSettings { + pub hue: f64, + pub gain: f64, + pub y_suppress: f64, + pub lift: f64, + pub narrow: bool, +} + +pub struct UpstreamKeyerAdvancedChromaSettings { + pub properties: Option, + pub sample: Option, +} + +pub struct UpstreamKeyerAdvancedChromaProperties { + pub foreground_level: f64, + pub background_level: f64, + pub key_edge: f64, + + pub spill_suppression: f64, + pub flare_suppression: f64, + + pub brightness: f64, + pub contrast: f64, + pub saturation: f64, + pub red: f64, + pub green: f64, + pub blue: f64, +} + +pub struct UpstreamKeyerAdvancedChromaSample { + pub enable_cursor: bool, + pub preview: bool, + pub cursor_x: f64, + pub cursor_y: f64, + pub cursor_size: f64, + pub sampled_y: f64, + pub sampled_cb: f64, + pub sampled_cr: f64, +} + +pub struct UpstreamKeyerLumaSettings { + pub pre_multiplied: bool, + pub clip: f64, + pub gain: f64, + pub invert: bool, +} + +pub struct UpstreamKeyerPatternSettings { + pub style: enums::Pattern, + pub size: f64, + pub symmetry: f64, + pub softness: f64, + pub position_x: f64, + pub position_y: f64, + pub invert: bool, +} + +pub struct UpstreamKeyerFlySettings { + is_a_set: bool, + is_b_set: bool, + is_at_key_frame: enums::IsAtKeyFrame, + run_to_infinite_index: f64, +} + +pub struct UpstreamKeyer { + pub mix_effect_key_type: enums::MixEffectKeyType, + pub fly_enabled: bool, + + upstream_keyer_id: f64, + can_fly_key: bool, + pub fill_source: f64, + pub cut_source: f64, + + pub dve_settings: Option, + pub chroma_settings: Option, + pub advanced_chroma_settings: Option, + pub luma_settings: Option, + pub pattern_settings: Option, + pub fly_keyframes: [Option; 2], + pub fly_properties: Option, + pub mask_settings: Box, + pub on_air: bool, + + pub mask_enabled: bool, + pub mask_top: f64, + pub mask_bottom: f64, + pub mask_left: f64, + pub mask_right: f64, + + pub size_x: f64, + pub size_y: f64, + pub position_x: f64, + pub position_y: f64, + pub rotation: f64, + + pub border_outer_width: f64, + pub border_inner_width: f64, + pub border_outer_softness: f64, + pub border_inner_softness: f64, + pub border_bevel_softness: f64, + pub border_bevel_position: f64, + + pub border_opacity: f64, + pub border_hue: f64, + pub border_saturation: f64, + pub border_luma: f64, + + pub light_source_direction: f64, + pub light_source_altitude: f64, +} + +impl UpstreamKeyerTypeSettings for UpstreamKeyer { + fn get_mix_effect_key_type(&self) -> enums::MixEffectKeyType { + self.mix_effect_key_type + } + + fn set_mix_effect_key_type(&mut self, key_type: enums::MixEffectKeyType) { + self.mix_effect_key_type = key_type + } + + fn get_fly_enabled(&self) -> bool { + self.fly_enabled + } + + fn set_fly_enabled(&mut self, enabled: bool) { + self.fly_enabled = enabled + } +} + +impl UpstreamKeyerBase for UpstreamKeyer { + fn get_upstream_keyer_id(&self) -> f64 { + self.upstream_keyer_id + } + + fn get_can_fly_key(&self) -> bool { + self.can_fly_key + } + + fn get_fill_source(&self) -> f64 { + self.fill_source + } + + fn set_fill_source(&mut self, source: f64) { + self.fill_source = source + } + + fn get_cut_source(&self) -> f64 { + self.cut_source + } + + fn set_cut_source(&mut self, source: f64) { + self.cut_source = source + } +} diff --git a/atem-test/Cargo.toml b/atem-test/Cargo.toml new file mode 100644 index 0000000..76f7cb0 --- /dev/null +++ b/atem-test/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "atem-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +atem-connection-rs = { path = "../atem-connection-rs" } +color-eyre = "0.5.11" +env_logger = "0.9.0" +tokio = "1.14.0" diff --git a/atem-test/src/main.rs b/atem-test/src/main.rs new file mode 100644 index 0000000..7f8285e --- /dev/null +++ b/atem-test/src/main.rs @@ -0,0 +1,44 @@ +use std::time::Duration; + +use atem_connection_rs::atem_lib::atem_socket::AtemSocket; + +use color_eyre::Report; +use tokio::time::sleep; + +#[tokio::main] +async fn main() { + setup_logging().unwrap(); + + let switch_raw_name: &str = "CPgI"; + let switch_source_to_1: [u8; 4] = [0, 0, 0, 1]; + let switch_source_to_2: [u8; 4] = [0, 0, 0, 2]; + + let mut atem = AtemSocket::new(); + atem.connect("127.0.0.1".to_string(), 9910).await.ok(); + + let mut tracking_id = 0; + loop { + sleep(Duration::from_millis(5000)).await; + atem.send_command(&switch_source_to_1, switch_raw_name, tracking_id) + .await; + tracking_id += 1; + sleep(Duration::from_millis(5000)).await; + atem.send_command(&switch_source_to_2, switch_raw_name, tracking_id) + .await; + tracking_id += 1; + } +} + +fn setup_logging() -> Result<(), Report> { + if std::env::var("RUST_LIB_BACKTRACE").is_err() { + std::env::set_var("RUST_LIB_BACKTRACE", "1"); + } + color_eyre::install()?; + + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "debug"); + } + env_logger::init(); + + Ok(()) +}