feat: setup for blossom uploads

refactor: move db to crate
This commit is contained in:
kieran 2024-11-15 17:26:20 +00:00
parent 0539468a5c
commit 9fdc1defaa
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
18 changed files with 2825 additions and 370 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
/target
**/target
.idea/
out/

353
Cargo.lock generated
View File

@ -251,6 +251,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -590,7 +596,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.4",
"windows-targets 0.52.6",
]
[[package]]
@ -747,6 +753,16 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -1106,6 +1122,21 @@ dependencies = [
"ttf-parser 0.21.1",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -1290,6 +1321,25 @@ dependencies = [
"tracing",
]
[[package]]
name = "h2"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap 2.2.5",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1506,7 +1556,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
@ -1529,6 +1579,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.6",
"http 1.1.0",
"http-body 1.0.1",
"httparse",
@ -1583,6 +1634,22 @@ dependencies = [
"tokio-io-timeout",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.5.0",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.10"
@ -1807,7 +1874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.52.4",
"windows-targets 0.48.5",
]
[[package]]
@ -1994,6 +2061,23 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "negentropy"
version = "0.3.1"
@ -2194,6 +2278,50 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
"bitflags 2.4.2",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "ordered-multimap"
version = "0.6.0"
@ -2230,7 +2358,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.4",
"windows-targets 0.52.6",
]
[[package]]
@ -2656,24 +2784,28 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.12.5"
version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.4.6",
"http 1.1.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.5.0",
"hyper-rustls 0.27.3",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
@ -2685,16 +2817,20 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.1",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.26.0",
"tokio-socks",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots",
"winreg",
"windows-registry",
]
[[package]]
@ -2949,6 +3085,15 @@ dependencies = [
"cipher",
]
[[package]]
name = "schannel"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -3004,6 +3149,29 @@ dependencies = [
"cc",
]
[[package]]
name = "security-framework"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.22"
@ -3537,6 +3705,30 @@ name = "sync_wrapper"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
dependencies = [
"futures-core",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.4.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "take-until"
@ -3693,6 +3885,16 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
@ -3825,7 +4027,7 @@ dependencies = [
"axum",
"base64 0.21.7",
"bytes",
"h2",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.31",
@ -4268,6 +4470,19 @@ version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "wasm-streams"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.72"
@ -4350,7 +4565,37 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.4",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
"windows-result",
"windows-strings",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result",
"windows-targets 0.52.6",
]
[[package]]
@ -4368,7 +4613,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.4",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
@ -4388,17 +4642,18 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
@ -4409,9 +4664,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@ -4421,9 +4676,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@ -4433,9 +4688,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@ -4445,9 +4706,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@ -4457,9 +4718,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@ -4469,9 +4730,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@ -4481,9 +4742,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
@ -4494,16 +4755,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "xmlwriter"
version = "0.1.0"
@ -4525,6 +4776,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"base64 0.22.1",
"chrono",
"clap",
"config",
@ -4532,6 +4784,7 @@ dependencies = [
"ffmpeg-rs-raw",
"fontdue",
"futures-util",
"hex",
"itertools 0.13.0",
"libc",
"log",
@ -4539,10 +4792,11 @@ dependencies = [
"nostr-sdk",
"pretty_env_logger",
"rand",
"reqwest",
"resvg",
"ringbuf",
"serde",
"sqlx",
"sha2",
"srt-tokio",
"tiny-skia",
"tokio",
@ -4551,6 +4805,17 @@ dependencies = [
"usvg",
"uuid",
"warp",
"zap-stream-db",
]
[[package]]
name = "zap-stream-db"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"log",
"sqlx",
]
[[package]]

View File

@ -10,8 +10,17 @@ path = "src/bin/zap_stream_core.rs"
[features]
default = ["test-pattern"]
srt = ["dep:srt-tokio"]
zap-stream = ["dep:nostr-sdk", "dep:sqlx", "dep:fedimint-tonic-lnd", "dep:chrono"]
test-pattern = ["dep:resvg", "dep:usvg", "dep:tiny-skia", "dep:fontdue", "dep:ringbuf"]
zap-stream = [
"dep:nostr-sdk",
"dep:zap-stream-db",
"dep:fedimint-tonic-lnd",
"dep:reqwest",
"tokio/fs",
"dep:base64",
"dep:sha2",
"dep:hex"
]
test-pattern = ["dep:resvg", "dep:usvg", "dep:tiny-skia", "dep:fontdue", "dep:ringbuf", "zap-stream-db/test-pattern"]
[dependencies]
ffmpeg-rs-raw = { git = "https://git.v0l.io/Kieran/ffmpeg-rs-raw.git", rev = "0abe0c5229adeb64b013d1895c7eba3d917f05a4" }
@ -32,6 +41,7 @@ clap = { version = "4.5.16", features = ["derive"] }
warp = "0.3.7"
libc = "0.2.162"
m3u8-rs = "6.0.0"
chrono = "^0.4.38"
# test-pattern
srt-tokio = { version = "0.4.3", optional = true }
@ -42,9 +52,10 @@ fontdue = { version = "0.9.2", optional = true }
ringbuf = { version = "0.4.7", optional = true }
# zap-stream
zap-stream-db = { path = "zap-stream-db", optional = true }
nostr-sdk = { version = "0.36.0", optional = true }
sqlx = { version = "0.8.2", optional = true, features = ["runtime-tokio", "migrate", "mysql", "chrono"] }
fedimint-tonic-lnd = { version = "0.2.0", optional = true, default-features = false, features = ["invoicesrpc", "versionrpc"] }
chrono = { version = "0.4.38", optional = true, features = ["serde"] }
reqwest = { version = "0.12.9", optional = true, features = ["stream"] }
base64 = { version = "0.22.1", optional = true }
sha2 = { version = "0.10.8", optional = true }
hex = { version = "0.4.3", optional = true }

2
db.sql Normal file
View File

@ -0,0 +1,2 @@
create database route96;
create database zap-stream;

View File

@ -4,14 +4,26 @@ services:
image: mariadb
environment:
- "MARIADB_ROOT_PASSWORD=root"
- "MARIADB_DATABASE=zap-stream"
ports:
- "3368:3306"
#volumes:
#- "db:/var/lib/mysql"
volumes:
- "./db.sql:/docker-entrypoint-initdb.d/00-init.sql"
relay:
image: scsibug/nostr-rs-relay
ports:
- "7766:8080"
blossom:
depends_on:
- db
image: voidic/route96
environment:
- "RUST_LOG=info"
ports:
- "8881:8000"
volumes:
- "blossom:/app/data"
- "./route96.toml:/app/config.toml"
volumes:
db:
blossom:
relay:

29
route96.toml Normal file
View File

@ -0,0 +1,29 @@
# Listen address for webserver
listen = "127.0.0.1:8000"
# Database connection string (MYSQL)
database = "mysql://root:root@db:3306/route96"
# Directory to store uploads
storage_dir = "./data"
# Maximum support filesize for uploading
max_upload_bytes = 5e+9
# Public facing url
public_url = "http://localhost:8881"
# Whitelisted pubkeys, leave out to disable
# whitelist = ["63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"]
# Path for ViT(224) image model (https://huggingface.co/google/vit-base-patch16-224)
# vit_model_path = "model.safetennsors"
# Webhook api endpoint
# webhook_url = "https://api.snort.social/api/v1/media/webhook"
# Analytics support
# plausible_url = "https://plausible.com/"
# Support legacy void
# void_cat_database = "postgres://postgres:postgres@localhost:41911/void"

93
src/blossom.rs Normal file
View File

@ -0,0 +1,93 @@
use std::collections::HashMap;
use anyhow::Result;
use base64::Engine;
use nostr_sdk::{EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::SeekFrom;
use std::ops::Add;
use std::path::PathBuf;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use url::Url;
pub struct Blossom {
url: Url,
client: reqwest::Client,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlobDescriptor {
pub url: String,
pub sha256: String,
pub size: u64,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
pub created: u64,
#[serde(rename = "nip94", skip_serializing_if = "Option::is_none")]
pub nip94: Option<HashMap<String, String>>,
}
impl Blossom {
pub fn new(url: &str) -> Self {
Self {
url: url.parse().unwrap(),
client: reqwest::Client::new(),
}
}
async fn hash_file(f: &mut File) -> Result<String> {
let mut hash = Sha256::new();
let mut buf: [u8; 1024] = [0; 1024];
f.seek(SeekFrom::Start(0)).await?;
while let Ok(data) = f.read(&mut buf).await {
if data == 0 {
break;
}
hash.update(&buf[..data]);
}
let hash = hash.finalize();
f.seek(SeekFrom::Start(0)).await?;
Ok(hex::encode(hash))
}
pub async fn upload(
&self,
from_file: &PathBuf,
keys: &Keys,
) -> Result<BlobDescriptor> {
let mut f = File::open(from_file).await?;
let hash = Self::hash_file(&mut f).await?;
let auth_event = EventBuilder::new(
Kind::Custom(24242),
"Upload blob",
[
Tag::hashtag("upload"),
Tag::parse(&["x", &hash])?,
Tag::expiration(Timestamp::now().add(60)),
],
);
let auth_event = auth_event.sign_with_keys(keys)?;
let rsp: BlobDescriptor = self
.client
.put(self.url.join("/upload").unwrap())
.header("Content-Type", "application/octet-stream")
.header(
"Authorization",
&format!(
"Nostr {}",
base64::engine::general_purpose::STANDARD
.encode(auth_event.as_json().as_bytes())
),
)
.body(f)
.send()
.await?
.json()
.await?;
Ok(rsp)
}
}

View File

@ -5,3 +5,4 @@ pub mod overseer;
pub mod pipeline;
pub mod settings;
pub mod variant;
mod blossom;

View File

@ -19,6 +19,7 @@ use std::sync::Arc;
use uuid::Uuid;
mod webhook;
#[cfg(feature = "zap-stream")]
mod zap_stream;

183
src/overseer/zap_stream.rs Normal file
View File

@ -0,0 +1,183 @@
use crate::blossom::Blossom;
use crate::egress::hls::HlsEgress;
use crate::egress::EgressConfig;
use crate::ingress::ConnectionInfo;
use crate::overseer::{get_default_variants, IngressInfo, Overseer};
use crate::pipeline::{EgressType, PipelineConfig};
use crate::settings::LndSettings;
use crate::variant::StreamMapping;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use chrono::Utc;
use fedimint_tonic_lnd::verrpc::VersionRequest;
use log::info;
use nostr_sdk::bitcoin::PrivateKey;
use nostr_sdk::{Client, Event, EventBuilder, JsonUtil, Keys, Kind, Tag};
use std::env::temp_dir;
use std::fs::create_dir_all;
use std::path::PathBuf;
use std::str::FromStr;
use uuid::Uuid;
use zap_stream_db::{UserStream, UserStreamState, ZapStreamDb};
/// zap.stream NIP-53 overseer
pub struct ZapStreamOverseer {
db: ZapStreamDb,
lnd: fedimint_tonic_lnd::Client,
client: Client,
keys: Keys,
}
impl ZapStreamOverseer {
pub async fn new(
private_key: &str,
db: &str,
lnd: &LndSettings,
relays: &Vec<String>,
) -> Result<Self> {
let db = ZapStreamDb::new(db).await?;
let mut lnd = fedimint_tonic_lnd::connect(
lnd.address.clone(),
PathBuf::from(&lnd.cert),
PathBuf::from(&lnd.macaroon),
)
.await?;
let version = lnd
.versioner()
.get_version(VersionRequest::default())
.await?;
info!("LND connected: v{}", version.into_inner().version);
let keys = Keys::from_str(private_key)?;
let client = nostr_sdk::ClientBuilder::new().signer(keys.clone()).build();
for r in relays {
client.add_relay(r).await?;
}
client.connect().await;
Ok(Self {
db,
lnd,
client,
keys,
})
}
}
#[async_trait]
impl Overseer for ZapStreamOverseer {
async fn start_stream(
&self,
connection: &ConnectionInfo,
stream_info: &IngressInfo,
) -> Result<PipelineConfig> {
let uid = self
.db
.find_user_stream_key(&connection.key)
.await?
.ok_or_else(|| anyhow::anyhow!("User not found"))?;
let out_dir = temp_dir().join("zap-stream");
create_dir_all(&out_dir)?;
let variants = get_default_variants(&stream_info)?;
let mut egress = vec![];
egress.push(EgressType::HLS(EgressConfig {
name: "nip94-hls".to_string(),
out_dir: out_dir.to_string_lossy().to_string(),
variants: variants.iter().map(|v| v.id()).collect(),
}));
// insert new stream record
let mut new_stream = UserStream {
id: 0,
user_id: uid,
starts: Utc::now(),
state: UserStreamState::Live,
..Default::default()
};
let stream_id = self.db.insert_stream(&new_stream).await?;
new_stream.id = stream_id;
let stream_event = publish_stream_event(&new_stream, &self.client).await?;
new_stream.event = Some(stream_event.as_json());
self.db.update_stream(&new_stream).await?;
Ok(PipelineConfig {
id: stream_id,
variants,
egress,
})
}
async fn on_segment(
&self,
pipeline: &Uuid,
variant_id: &Uuid,
index: u64,
duration: f32,
path: &PathBuf,
) -> Result<()> {
let blossom = Blossom::new("http://localhost:8881/");
let blob = blossom.upload(path, &self.keys).await?;
Ok(())
}
}
pub(super) fn to_event_builder(this: &UserStream) -> Result<EventBuilder> {
let mut tags = vec![
Tag::parse(&["d".to_string(), this.id.to_string()])?,
Tag::parse(&["status".to_string(), this.state.to_string()])?,
Tag::parse(&["starts".to_string(), this.starts.timestamp().to_string()])?,
];
if let Some(ref ends) = this.ends {
tags.push(Tag::parse(&[
"ends".to_string(),
ends.timestamp().to_string(),
])?);
}
if let Some(ref title) = this.title {
tags.push(Tag::parse(&["title".to_string(), title.to_string()])?);
}
if let Some(ref summary) = this.summary {
tags.push(Tag::parse(&["summary".to_string(), summary.to_string()])?);
}
if let Some(ref image) = this.image {
tags.push(Tag::parse(&["image".to_string(), image.to_string()])?);
}
if let Some(ref thumb) = this.thumb {
tags.push(Tag::parse(&["thumb".to_string(), thumb.to_string()])?);
}
if let Some(ref content_warning) = this.content_warning {
tags.push(Tag::parse(&[
"content_warning".to_string(),
content_warning.to_string(),
])?);
}
if let Some(ref goal) = this.goal {
tags.push(Tag::parse(&["goal".to_string(), goal.to_string()])?);
}
if let Some(ref pinned) = this.pinned {
tags.push(Tag::parse(&["pinned".to_string(), pinned.to_string()])?);
}
if let Some(ref tags_csv) = this.tags {
for tag in tags_csv.split(',') {
tags.push(Tag::parse(&["t".to_string(), tag.to_string()])?);
}
}
Ok(EventBuilder::new(Kind::from(30_313), "", tags))
}
pub(super) async fn publish_stream_event(this: &UserStream, client: &Client) -> Result<Event> {
let ev = to_event_builder(this)?
.sign(&client.signer().await?)
.await?;
client.send_event(ev.clone()).await?;
Ok(ev)
}

View File

@ -1,114 +0,0 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use nostr_sdk::{Client, Event, EventBuilder, Kind, Tag};
use sqlx::{FromRow, Type};
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, FromRow)]
pub struct User {
pub id: u64,
pub pubkey: [u8; 32],
pub created: DateTime<Utc>,
pub balance: i64,
pub tos_accepted: DateTime<Utc>,
pub stream_key: String,
pub is_admin: bool,
pub is_blocked: bool,
}
#[derive(Default, Debug, Clone, Type)]
#[repr(u8)]
pub enum UserStreamState {
#[default]
Unknown = 0,
Planned = 1,
Live = 2,
Ended = 3,
}
impl Display for UserStreamState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UserStreamState::Unknown => write!(f, "unknown"),
UserStreamState::Planned => write!(f, "planned"),
UserStreamState::Live => write!(f, "live"),
UserStreamState::Ended => write!(f, "ended"),
}
}
}
#[derive(Debug, Clone, Default, FromRow)]
pub struct UserStream {
pub id: u64,
pub user_id: u64,
pub starts: DateTime<Utc>,
pub ends: Option<DateTime<Utc>>,
pub state: UserStreamState,
pub title: Option<String>,
pub summary: Option<String>,
pub image: Option<String>,
pub thumb: Option<String>,
pub tags: Option<String>,
pub content_warning: Option<String>,
pub goal: Option<String>,
pub pinned: Option<String>,
pub cost: u64,
pub duration: f32,
pub fee: Option<u32>,
pub event: Option<String>,
}
impl UserStream {
pub(crate) fn to_event_builder(&self) -> Result<EventBuilder> {
let mut tags = vec![
Tag::parse(&["d".to_string(), self.id.to_string()])?,
Tag::parse(&["status".to_string(), self.state.to_string()])?,
Tag::parse(&["starts".to_string(), self.starts.timestamp().to_string()])?,
];
if let Some(ref ends) = self.ends {
tags.push(Tag::parse(&[
"ends".to_string(),
ends.timestamp().to_string(),
])?);
}
if let Some(ref title) = self.title {
tags.push(Tag::parse(&["title".to_string(), title.to_string()])?);
}
if let Some(ref summary) = self.summary {
tags.push(Tag::parse(&["summary".to_string(), summary.to_string()])?);
}
if let Some(ref image) = self.image {
tags.push(Tag::parse(&["image".to_string(), image.to_string()])?);
}
if let Some(ref thumb) = self.thumb {
tags.push(Tag::parse(&["thumb".to_string(), thumb.to_string()])?);
}
if let Some(ref content_warning) = self.content_warning {
tags.push(Tag::parse(&[
"content_warning".to_string(),
content_warning.to_string(),
])?);
}
if let Some(ref goal) = self.goal {
tags.push(Tag::parse(&["goal".to_string(), goal.to_string()])?);
}
if let Some(ref pinned) = self.pinned {
tags.push(Tag::parse(&["pinned".to_string(), pinned.to_string()])?);
}
if let Some(ref tags_csv) = self.tags {
for tag in tags_csv.split(',') {
tags.push(Tag::parse(&["t".to_string(), tag.to_string()])?);
}
}
Ok(EventBuilder::new(Kind::from(30_313), "", tags))
}
pub(super) async fn publish_stream_event(&self, client: &Client) -> Result<Event> {
let ev = self
.to_event_builder()?
.sign(&client.signer().await?)
.await?;
client.send_event(ev.clone()).await?;
Ok(ev)
}
}

View File

@ -1,201 +0,0 @@
use crate::egress::hls::HlsEgress;
use crate::egress::EgressConfig;
use crate::ingress::ConnectionInfo;
use crate::overseer::zap_stream::db::{UserStream, UserStreamState};
use crate::overseer::{get_default_variants, IngressInfo, Overseer};
use crate::pipeline::{EgressType, PipelineConfig};
use crate::settings::LndSettings;
use crate::variant::StreamMapping;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use fedimint_tonic_lnd::verrpc::VersionRequest;
use log::info;
use nostr_sdk::bitcoin::PrivateKey;
use nostr_sdk::{JsonUtil, Keys};
use sqlx::{MySqlPool, Row};
use std::env::temp_dir;
use std::fs::create_dir_all;
use std::path::PathBuf;
use std::str::FromStr;
use uuid::Uuid;
mod db;
/// zap.stream NIP-53 overseer
#[derive(Clone)]
pub struct ZapStreamOverseer {
db: MySqlPool,
lnd: fedimint_tonic_lnd::Client,
client: nostr_sdk::Client,
keys: Keys,
}
impl ZapStreamOverseer {
pub async fn new(
private_key: &str,
db: &str,
lnd: &LndSettings,
relays: &Vec<String>,
) -> Result<Self> {
let db = MySqlPool::connect(db).await?;
info!("Connected to database, running migrations");
// automatically run migrations
sqlx::migrate!().run(&db).await?;
let mut lnd = fedimint_tonic_lnd::connect(
lnd.address.clone(),
PathBuf::from(&lnd.cert),
PathBuf::from(&lnd.macaroon),
)
.await?;
let version = lnd
.versioner()
.get_version(VersionRequest::default())
.await?;
info!("LND connected: v{}", version.into_inner().version);
let keys = Keys::from_str(private_key)?;
let client = nostr_sdk::ClientBuilder::new().signer(keys.clone()).build();
for r in relays {
client.add_relay(r).await?;
}
client.connect().await;
Ok(Self {
db,
lnd,
client,
keys,
})
}
/// Find user by stream key, typical first lookup from ingress
async fn find_user_stream_key(&self, key: &str) -> Result<Option<u64>> {
#[cfg(feature = "test-pattern")]
if key == "test-pattern" {
// use the 00 pubkey for test sources
return Ok(Some(self.upsert_user(&[0; 32]).await?));
}
Ok(sqlx::query("select id from user where stream_key = ?")
.bind(key)
.fetch_optional(&self.db)
.await?
.map(|r| r.try_get(0).unwrap()))
}
async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
let res = sqlx::query("insert ignore into user(pubkey) values(?) returning id")
.bind(pubkey.as_slice())
.fetch_optional(&self.db)
.await?;
match res {
None => sqlx::query("select id from user where pubkey = ?")
.bind(pubkey.as_slice())
.fetch_one(&self.db)
.await?
.try_get(0)
.map_err(anyhow::Error::new),
Some(res) => res.try_get(0).map_err(anyhow::Error::new),
}
}
async fn create_stream(&self, user_stream: &UserStream) -> Result<u64> {
sqlx::query(
"insert into user_stream (user_id, state, starts) values (?, ?, ?) returning id",
)
.bind(&user_stream.user_id)
.bind(&user_stream.state)
.bind(&user_stream.starts)
.fetch_one(&self.db)
.await?
.try_get(0)
.map_err(anyhow::Error::new)
}
async fn update_stream(&self, user_stream: &UserStream) -> Result<()> {
sqlx::query(
"update user_stream set state = ?, starts = ?, ends = ?, title = ?, summary = ?, image = ?, thumb = ?, tags = ?, content_warning = ?, goal = ?, pinned = ?, fee = ?, event = ? where id = ?",
)
.bind(&user_stream.state)
.bind(&user_stream.starts)
.bind(&user_stream.ends)
.bind(&user_stream.title)
.bind(&user_stream.summary)
.bind(&user_stream.image)
.bind(&user_stream.thumb)
.bind(&user_stream.tags)
.bind(&user_stream.content_warning)
.bind(&user_stream.goal)
.bind(&user_stream.pinned)
.bind(&user_stream.fee)
.bind(&user_stream.event)
.bind(&user_stream.id)
.execute(&self.db)
.await
.map_err(anyhow::Error::new)?;
Ok(())
}
}
#[async_trait]
impl Overseer for ZapStreamOverseer {
async fn start_stream(
&self,
connection: &ConnectionInfo,
stream_info: &IngressInfo,
) -> Result<PipelineConfig> {
let uid = self
.find_user_stream_key(&connection.key)
.await?
.ok_or_else(|| anyhow::anyhow!("User not found"))?;
let out_dir = temp_dir().join("zap-stream");
create_dir_all(&out_dir)?;
let variants = get_default_variants(&stream_info)?;
let mut egress = vec![];
egress.push(EgressType::HLS(EgressConfig {
name: "nip94-hls".to_string(),
out_dir: out_dir.to_string_lossy().to_string(),
variants: variants.iter().map(|v| v.id()).collect(),
}));
// insert new stream record
let mut new_stream = UserStream {
id: 0,
user_id: uid,
starts: Utc::now(),
state: UserStreamState::Live,
..Default::default()
};
let stream_id = self.create_stream(&new_stream).await?;
new_stream.id = stream_id;
let stream_event = new_stream.publish_stream_event(&self.client).await?;
new_stream.event = Some(stream_event.as_json());
self.update_stream(&new_stream).await?;
Ok(PipelineConfig {
id: stream_id,
variants,
egress,
})
}
async fn on_segment(
&self,
pipeline: &Uuid,
variant_id: &Uuid,
index: u64,
duration: f32,
path: &PathBuf,
) -> Result<()> {
todo!()
}
}

2007
zap-stream-db/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
zap-stream-db/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "zap-stream-db"
version = "0.1.0"
edition = "2021"
[features]
default = []
test-pattern = []
[dependencies]
anyhow = "^1.0.70"
chrono = { version = "0.4.38", features = ["serde"] }
sqlx = { version = "0.8.2", features = ["runtime-tokio", "migrate", "mysql", "chrono"] }
log = "0.4.22"

88
zap-stream-db/src/db.rs Normal file
View File

@ -0,0 +1,88 @@
use crate::UserStream;
use anyhow::Result;
use log::info;
use sqlx::{MySqlPool, Row};
pub struct ZapStreamDb {
db: MySqlPool,
}
impl ZapStreamDb {
pub async fn new(db: &str) -> Result<Self> {
let db = MySqlPool::connect(db).await?;
Ok(ZapStreamDb { db })
}
pub async fn migrate(&self) -> Result<()> {
sqlx::migrate!().run(&self.db).await?;
Ok(())
}
/// Find user by stream key, typical first lookup from ingress
pub async fn find_user_stream_key(&self, key: &str) -> Result<Option<u64>> {
#[cfg(feature = "test-pattern")]
if key == "test-pattern" {
// use the 00 pubkey for test sources
return Ok(Some(self.upsert_user(&[0; 32]).await?));
}
Ok(sqlx::query("select id from user where stream_key = ?")
.bind(key)
.fetch_optional(&self.db)
.await?
.map(|r| r.try_get(0).unwrap()))
}
pub async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
let res = sqlx::query("insert ignore into user(pubkey) values(?) returning id")
.bind(pubkey.as_slice())
.fetch_optional(&self.db)
.await?;
match res {
None => sqlx::query("select id from user where pubkey = ?")
.bind(pubkey.as_slice())
.fetch_one(&self.db)
.await?
.try_get(0)
.map_err(anyhow::Error::new),
Some(res) => res.try_get(0).map_err(anyhow::Error::new),
}
}
pub async fn insert_stream(&self, user_stream: &UserStream) -> Result<u64> {
sqlx::query(
"insert into user_stream (user_id, state, starts) values (?, ?, ?) returning id",
)
.bind(&user_stream.user_id)
.bind(&user_stream.state)
.bind(&user_stream.starts)
.fetch_one(&self.db)
.await?
.try_get(0)
.map_err(anyhow::Error::new)
}
pub async fn update_stream(&self, user_stream: &UserStream) -> Result<()> {
sqlx::query(
"update user_stream set state = ?, starts = ?, ends = ?, title = ?, summary = ?, image = ?, thumb = ?, tags = ?, content_warning = ?, goal = ?, pinned = ?, fee = ?, event = ? where id = ?",
)
.bind(&user_stream.state)
.bind(&user_stream.starts)
.bind(&user_stream.ends)
.bind(&user_stream.title)
.bind(&user_stream.summary)
.bind(&user_stream.image)
.bind(&user_stream.thumb)
.bind(&user_stream.tags)
.bind(&user_stream.content_warning)
.bind(&user_stream.goal)
.bind(&user_stream.pinned)
.bind(&user_stream.fee)
.bind(&user_stream.event)
.bind(&user_stream.id)
.execute(&self.db)
.await
.map_err(anyhow::Error::new)?;
Ok(())
}
}

7
zap-stream-db/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
mod db;
mod model;
pub use db::*;
pub use model::*;
pub use sqlx;

View File

@ -0,0 +1,57 @@
use chrono::{DateTime, Utc};
use sqlx::{FromRow, Type};
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, FromRow)]
pub struct User {
pub id: u64,
pub pubkey: [u8; 32],
pub created: DateTime<Utc>,
pub balance: i64,
pub tos_accepted: DateTime<Utc>,
pub stream_key: String,
pub is_admin: bool,
pub is_blocked: bool,
}
#[derive(Default, Debug, Clone, Type)]
#[repr(u8)]
pub enum UserStreamState {
#[default]
Unknown = 0,
Planned = 1,
Live = 2,
Ended = 3,
}
impl Display for UserStreamState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UserStreamState::Unknown => write!(f, "unknown"),
UserStreamState::Planned => write!(f, "planned"),
UserStreamState::Live => write!(f, "live"),
UserStreamState::Ended => write!(f, "ended"),
}
}
}
#[derive(Debug, Clone, Default, FromRow)]
pub struct UserStream {
pub id: u64,
pub user_id: u64,
pub starts: DateTime<Utc>,
pub ends: Option<DateTime<Utc>>,
pub state: UserStreamState,
pub title: Option<String>,
pub summary: Option<String>,
pub image: Option<String>,
pub thumb: Option<String>,
pub tags: Option<String>,
pub content_warning: Option<String>,
pub goal: Option<String>,
pub pinned: Option<String>,
pub cost: u64,
pub duration: f32,
pub fee: Option<u32>,
pub event: Option<String>,
}