diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5d664e723..06a126418 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -81,7 +81,11 @@ jobs: - uses: davidB/rust-cargo-make@v1 - uses: cargo-bins/cargo-binstall@main - name: Install cargo-component - run: cargo binstall --force --locked cargo-component@0.21.1 + run: | + set -e + cargo binstall --force --locked cargo-component@0.21.1 + cargo binstall wac-cli --locked --force --no-confirm + cargo binstall golem-cli@1.2.5 --locked --force --no-confirm - name: Build all test components run: cargo make build-test-components ollama-integration-tests: @@ -108,26 +112,25 @@ jobs: run: | set -e cargo binstall --force --locked cargo-component@0.21.1 - cargo binstall golem-cli@1.2.3 --locked --force --no-confirm cargo binstall wac-cli --locked --force --no-confirm - name: Start Ollama in Docker run: | set -e docker run -d --name ollama -p 11434:11434 ollama/ollama:latest timeout 60 bash -c 'until curl -f http://localhost:11434/api/version; do sleep 2; done' - echo "Pulling Qwen2.5:1.5b" - docker exec ollama ollama pull qwen2.5:1.5b - echo "Pulling Gemma2:2b" - docker exec ollama ollama pull gemma2:2b + echo "Pulling Qwen3:1.7b" + docker exec ollama ollama pull qwen3:1.7b + echo "Pulling Gemma3:4b" + docker exec ollama ollama pull gemma3:4b echo "Verifying models are available" - docker exec ollama ollama list | grep -q "qwen2.5:1.5b" || exit 1 - docker exec ollama ollama list | grep -q "gemma2:2b" || exit 1 + docker exec ollama ollama list | grep -q "qwen3:1.7b" || exit 1 + docker exec ollama ollama list | grep -q "gemma3:4b" || exit 1 echo "Ollama setup completed." - name: Install and Run latest Golem Server run: | set -e echo "Installing Golem server" - sudo curl -L https://github.com/golemcloud/golem-cli/releases/download/v1.2.3/golem-x86_64-unknown-linux-gnu -o ./golem + sudo curl -L https://github.com/golemcloud/golem-cli/releases/download/v1.2.5/golem-x86_64-unknown-linux-gnu -o ./golem sudo chmod +x ./golem sudo mv ./golem /usr/local/bin/golem golem --version @@ -138,17 +141,17 @@ jobs: run: | set -e cargo make --cwd llm build-ollama - cd test - golem-cli app build -b ollama-debug - golem-cli app deploy -b ollama-debug - golem-cli worker new -e GOLEM_OLLAMA_BASE_URL=http://localhost:11434 test:llm/ollama-1 - golem-cli worker invoke test:llm/ollama-1 test1 - golem-cli worker invoke test:llm/ollama-1 test2 - golem-cli worker invoke test:llm/ollama-1 test3 - golem-cli worker invoke test:llm/ollama-1 test4 - golem-cli worker invoke test:llm/ollama-1 test5 - golem-cli worker invoke test:llm/ollama-1 test6 - golem-cli worker invoke test:llm/ollama-1 test7 + cd test/llm + golem app build -b ollama-debug + golem app deploy -b ollama-debug + golem worker new -e GOLEM_OLLAMA_BASE_URL=http://localhost:11434 test:llm/ollama-1 + golem worker invoke test:llm/ollama-1 test1 + golem worker invoke test:llm/ollama-1 test2 + golem worker invoke test:llm/ollama-1 test3 + golem worker invoke test:llm/ollama-1 test4 + golem worker invoke test:llm/ollama-1 test6 + golem worker invoke test:llm/ollama-1 test5 + golem worker invoke test:llm/ollama-1 test7 publish-all: needs: - tests diff --git a/Cargo.lock b/Cargo.lock index 0865d6ade..b27724975 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,15 +49,21 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64" -version = "0.22.1" +name = "base64ct" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" @@ -65,12 +71,39 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -139,12 +172,27 @@ dependencies = [ "windows-link", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -154,6 +202,44 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -180,6 +266,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "flate2" version = "1.1.1" @@ -300,6 +395,27 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -309,7 +425,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -350,7 +466,7 @@ dependencies = [ name = "golem-llm-anthropic" version = "0.0.0" dependencies = [ - "base64 0.22.1", + "base64", "golem-llm", "golem-rust", "log", @@ -364,7 +480,7 @@ dependencies = [ name = "golem-llm-grok" version = "0.0.0" dependencies = [ - "base64 0.22.1", + "base64", "golem-llm", "golem-rust", "log", @@ -378,7 +494,7 @@ dependencies = [ name = "golem-llm-ollama" version = "0.0.0" dependencies = [ - "base64 0.21.7", + "base64", "golem-llm", "golem-rust", "log", @@ -394,7 +510,7 @@ dependencies = [ name = "golem-llm-openai" version = "0.0.0" dependencies = [ - "base64 0.22.1", + "base64", "golem-llm", "golem-rust", "log", @@ -408,7 +524,7 @@ dependencies = [ name = "golem-llm-openrouter" version = "0.0.0" dependencies = [ - "base64 0.22.1", + "base64", "golem-llm", "golem-rust", "log", @@ -444,6 +560,83 @@ dependencies = [ "syn", ] +[[package]] +name = "golem-video" +version = "0.0.0" +dependencies = [ + "golem-rust", + "log", + "mime", + "nom", + "reqwest", + "thiserror", + "wasi-logger", + "wit-bindgen 0.40.0", +] + +[[package]] +name = "golem-video-kling" +version = "0.0.0" +dependencies = [ + "base64", + "golem-rust", + "golem-video", + "hmac-sha256", + "log", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt 0.40.0", +] + +[[package]] +name = "golem-video-runway" +version = "0.0.0" +dependencies = [ + "base64", + "golem-rust", + "golem-video", + "log", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt 0.40.0", +] + +[[package]] +name = "golem-video-stability" +version = "0.0.0" +dependencies = [ + "base64", + "golem-rust", + "golem-video", + "image", + "log", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt 0.40.0", +] + +[[package]] +name = "golem-video-veo" +version = "0.0.0" +dependencies = [ + "base64", + "data-encoding", + "golem-rust", + "golem-video", + "log", + "pkcs8", + "reqwest", + "rsa", + "serde", + "serde_json", + "sha2", + "urlencoding", + "wit-bindgen-rt 0.40.0", +] + [[package]] name = "golem-wasm-rpc" version = "1.3.0-dev.6" @@ -481,6 +674,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hmac-sha256" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" + [[package]] name = "http" version = "1.3.1" @@ -629,6 +828,20 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -656,6 +869,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "leb128" version = "0.2.5" @@ -674,6 +896,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "litemap" version = "0.8.0" @@ -721,6 +949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -733,6 +962,43 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -740,6 +1006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -748,6 +1015,15 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -766,6 +1042,40 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -775,6 +1085,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.32" @@ -809,12 +1128,41 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "reqwest" version = "0.12.15" source = "git+https://github.com/golemcloud/reqwest?branch=update-may-2025#c916d796a87bdace112d24b862142597d1074305" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", @@ -831,6 +1179,26 @@ dependencies = [ "wit-bindgen-rt 0.41.0", ] +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -902,12 +1270,39 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -932,12 +1327,34 @@ dependencies = [ "smallvec", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.101" @@ -1011,6 +1428,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicase" version = "2.8.1" @@ -1046,6 +1469,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -1058,13 +1487,25 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom", + "getrandom 0.3.3", "js-sys", "serde", "sha1_smol", "wasm-bindgen", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -1202,7 +1643,7 @@ version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" dependencies = [ - "bitflags", + "bitflags 2.9.1", "indexmap", "semver", ] @@ -1213,7 +1654,7 @@ version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ - "bitflags", + "bitflags 2.9.1", "hashbrown", "indexmap", "semver", @@ -1325,7 +1766,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0780cf7046630ed70f689a098cd8d56c5c3b22f2a7379bbdb088879963ff96" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] @@ -1334,7 +1775,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] @@ -1343,7 +1784,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68faed92ae696b93ea9a7b67ba6c37bf09d72c6d9a70fa824a743c3020212f11" dependencies = [ - "bitflags", + "bitflags 2.9.1", "futures", "once_cell", ] @@ -1354,7 +1795,7 @@ version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] @@ -1423,7 +1864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c836b1fd9932de0431c1758d8be08212071b6bba0151f7bac826dbc4312a2a9" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.9.1", "indexmap", "log", "serde", @@ -1442,7 +1883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.9.1", "indexmap", "log", "serde", @@ -1520,6 +1961,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -1541,6 +2002,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerotrie" version = "0.2.2" @@ -1573,3 +2040,18 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 7bea1e1e5..bf89b48c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,11 @@ members = [ "llm/ollama", "llm/openai", "llm/openrouter", + "video/video", + "video/veo", + "video/stability", + "video/kling", + "video/runway", ] [profile.release] @@ -26,3 +31,5 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } wit-bindgen-rt = { version = "0.40.0", features = ["bitflags"] } base64 = { version = "0.22.1" } +url = { version = "2.5.4" } +mime_guess = { version = "2.0.5" } \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml index cc443bc6a..e4961cc8e 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -13,7 +13,7 @@ args = ["test"] [tasks.build] script_runner = "@duckscript" script = ''' -domains = array llm +domains = array llm video # if there is no domain passed run for every domain if is_empty ${1} @@ -28,7 +28,7 @@ end [tasks.release-build] script_runner = "@duckscript" script = ''' -domains = array llm +domains = array llm video # if there is no domain passed run for every domain if is_empty ${1} @@ -44,7 +44,7 @@ end script_runner = "@duckscript" script = ''' #!/bin/bash -domains = array llm +domains = array llm video # if there is no domain passed run for every domain if is_empty ${1} @@ -60,7 +60,7 @@ end script_runner = "@duckscript" script = ''' #!/bin/bash -domains = array llm +domains = array llm video # if there is no domain passed run for every domain if is_empty ${1} @@ -75,7 +75,7 @@ end [tasks.wit] script_runner = "@duckscript" script = ''' -domains = array llm +domains = array llm video # if there is no domain passed run for every domain if is_empty ${1} @@ -88,19 +88,40 @@ end ''' [tasks.build-test-components] +dependencies = ["build"] description = "Builds all test components with golem-cli" -script_runner = "@duckscript" script = ''' -domains = array llm - -# if there is no domain passed run for every domain -if is_empty ${1} - for domain in ${domains} - exec cargo make --cwd ${domain} build-test-components - end -else - exec cargo make --cwd ${1} build-test-components -end +cd test/llm + +golem-cli --version +golem-cli app clean +golem-cli app build -b anthropic-debug +golem-cli app clean +golem-cli app build -b grok-debug +golem-cli app clean +golem-cli app build -b openai-debug +golem-cli app clean +golem-cli app build -b openrouter-debug +golem-cli app clean +golem-cli app build -b ollama-debug +golem-cli app clean + +cd ../video +golem-cli --version +golem-cli app clean +golem-cli app build -b veo-debug +golem-cli app clean +golem-cli app build -b stability-debug +golem-cli app clean +golem-cli app build -b kling-debug +golem-cli app clean +golem-cli app build -b runway-debug +golem-cli app clean + +cd ../video-advanced +golem-cli --version +golem-cli app clean +golem-cli app build ''' [tasks.build-all] @@ -108,11 +129,37 @@ script_runner = "@duckscript" script = ''' mkdir components/debug +cm_run_task clean cm_run_task build -cm_run_task copy-debug-artifacts +# Copy LLM artifacts +cp target/wasm32-wasip1/debug/golem_llm_anthropic.wasm components/debug/golem_llm_anthropic.wasm +cp target/wasm32-wasip1/debug/golem_llm_grok.wasm components/debug/golem_llm_grok.wasm +cp target/wasm32-wasip1/debug/golem_llm_openai.wasm components/debug/golem_llm_openai.wasm +cp target/wasm32-wasip1/debug/golem_llm_openrouter.wasm components/debug/golem_llm_openrouter.wasm +cp target/wasm32-wasip1/debug/golem_llm_ollama.wasm components/debug/golem_llm_ollama.wasm + +# Copy Video artifacts +cp target/wasm32-wasip1/debug/golem_video_veo.wasm components/debug/golem_video_veo.wasm +cp target/wasm32-wasip1/debug/golem_video_stability.wasm components/debug/golem_video_stability.wasm +cp target/wasm32-wasip1/debug/golem_video_kling.wasm components/debug/golem_video_kling.wasm +cp target/wasm32-wasip1/debug/golem_video_runway.wasm components/debug/golem_video_runway.wasm + +cm_run_task clean cm_run_task build-portable -cm_run_task copy-debug-artifacts --portable + +# Copy portable LLM artifacts +cp target/wasm32-wasip1/debug/golem_llm_anthropic.wasm components/debug/golem_llm_anthropic-portable.wasm +cp target/wasm32-wasip1/debug/golem_llm_grok.wasm components/debug/golem_llm_grok-portable.wasm +cp target/wasm32-wasip1/debug/golem_llm_openai.wasm components/debug/golem_llm_openai-portable.wasm +cp target/wasm32-wasip1/debug/golem_llm_openrouter.wasm components/debug/golem_llm_openrouter-portable.wasm +cp target/wasm32-wasip1/debug/golem_llm_ollama.wasm components/debug/golem_llm_ollama-portable.wasm + +# Copy portable Video artifacts +cp target/wasm32-wasip1/debug/golem_video_veo.wasm components/debug/golem_video_veo-portable.wasm +cp target/wasm32-wasip1/debug/golem_video_stability.wasm components/debug/golem_video_stability-portable.wasm +cp target/wasm32-wasip1/debug/golem_video_kling.wasm components/debug/golem_video_kling-portable.wasm +cp target/wasm32-wasip1/debug/golem_video_runway.wasm components/debug/golem_video_runway-portable.wasm ''' [tasks.release-build-all] @@ -124,43 +171,35 @@ cm_run_task set-version cm_run_task clean cm_run_task release-build -cm_run_task copy-release-artifacts - -cm_run_task clean -cm_run_task release-build-portable -cm_run_task copy-release-artifacts --portable -''' - -[tasks.copy-debug-artifacts] -script_runner = "@duckscript" -script = ''' -is_portable = eq ${1} "--portable" +# Copy LLM artifacts +cp target/wasm32-wasip1/release/golem_llm_anthropic.wasm components/release/golem_llm_anthropic.wasm +cp target/wasm32-wasip1/release/golem_llm_grok.wasm components/release/golem_llm_grok.wasm +cp target/wasm32-wasip1/release/golem_llm_openai.wasm components/release/golem_llm_openai.wasm +cp target/wasm32-wasip1/release/golem_llm_openrouter.wasm components/release/golem_llm_openrouter.wasm +cp target/wasm32-wasip1/release/golem_llm_ollama.wasm components/release/golem_llm_ollama.wasm -targets = array llm_openai llm_anthropic llm_grok llm_openrouter llm_ollama -for target in ${targets} - if is_portable - cp target/wasm32-wasip1/debug/golem_${target}.wasm components/debug/golem_${target}-portable.wasm - else - cp target/wasm32-wasip1/debug/golem_${target}.wasm components/debug/golem_${target}.wasm - end -end -''' +# Copy Video artifacts +cp target/wasm32-wasip1/release/golem_video_veo.wasm components/release/golem_video_veo.wasm +cp target/wasm32-wasip1/release/golem_video_stability.wasm components/release/golem_video_stability.wasm +cp target/wasm32-wasip1/release/golem_video_kling.wasm components/release/golem_video_kling.wasm +cp target/wasm32-wasip1/release/golem_video_runway.wasm components/release/golem_video_runway.wasm -[tasks.copy-release-artifacts] -script_runner = "@duckscript" -script = ''' - -is_portable = eq ${1} "--portable" +cm_run_task clean +cm_run_task release-build-portable -targets = array llm_openai llm_anthropic llm_grok llm_openrouter llm_ollama -for target in ${targets} - if is_portable - cp target/wasm32-wasip1/release/golem_${target}.wasm components/release/golem_${target}-portable.wasm - else - cp target/wasm32-wasip1/release/golem_${target}.wasm components/release/golem_${target}.wasm - end -end +# Copy portable LLM artifacts +cp target/wasm32-wasip1/release/golem_llm_anthropic.wasm components/release/golem_llm_anthropic-portable.wasm +cp target/wasm32-wasip1/release/golem_llm_grok.wasm components/release/golem_llm_grok-portable.wasm +cp target/wasm32-wasip1/release/golem_llm_openai.wasm components/release/golem_llm_openai-portable.wasm +cp target/wasm32-wasip1/release/golem_llm_openrouter.wasm components/release/golem_llm_openrouter-portable.wasm +cp target/wasm32-wasip1/release/golem_llm_ollama.wasm components/release/golem_llm_ollama-portable.wasm + +# Copy portable Video artifacts +cp target/wasm32-wasip1/release/golem_video_veo.wasm components/release/golem_video_veo-portable.wasm +cp target/wasm32-wasip1/release/golem_video_stability.wasm components/release/golem_video_stability-portable.wasm +cp target/wasm32-wasip1/release/golem_video_kling.wasm components/release/golem_video_kling-portable.wasm +cp target/wasm32-wasip1/release/golem_video_runway.wasm components/release/golem_video_runway-portable.wasm ''' # Maintenance tasks diff --git a/llm/Makefile.toml b/llm/Makefile.toml index 5b7b92a89..56e755166 100644 --- a/llm/Makefile.toml +++ b/llm/Makefile.toml @@ -175,36 +175,15 @@ for module in ${modules} end # Copy WIT files for integration tests -rm -r ../test/wit -mkdir ../test/wit/deps/golem-llm -mkdir ../test/wit/deps/io -cp wit/golem-llm.wit ../test/wit/deps/golem-llm/golem-llm.wit -cp wit/deps/wasi:io/error.wit ../test/wit/deps/io/error.wit -cp wit/deps/wasi:io/poll.wit ../test/wit/deps/io/poll.wit -cp wit/deps/wasi:io/streams.wit ../test/wit/deps/io/streams.wit -cp wit/deps/wasi:io/world.wit ../test/wit/deps/io/world.wit +rm -r ../test/llm/wit +mkdir ../test/llm/wit/deps/golem-llm +mkdir ../test/llm/wit/deps/io +cp wit/golem-llm.wit ../test/llm/wit/deps/golem-llm/golem-llm.wit +cp wit/deps/wasi:io/error.wit ../test/llm/wit/deps/io/error.wit +cp wit/deps/wasi:io/poll.wit ../test/llm/wit/deps/io/poll.wit +cp wit/deps/wasi:io/streams.wit ../test/llm/wit/deps/io/streams.wit +cp wit/deps/wasi:io/world.wit ../test/llm/wit/deps/io/world.wit echo "Copied WIT for module test" """ -[tasks.build-test-components] -dependencies = ["build"] -install_crate = "cargo-binstall" -description = "Builds llm test components with golem-cli" -script = ''' -cargo-binstall golem-cli@1.2.2-dev.11 --locked --no-confirm -cargo-binstall wac-cli --locked --no-confirm -cd ../test - -golem-cli --version -golem-cli app clean -golem-cli app build -b anthropic-debug -golem-cli app clean -golem-cli app build -b grok-debug -golem-cli app clean -golem-cli app build -b openai-debug -golem-cli app clean -golem-cli app build -b openrouter-debug -golem-cli app clean -golem-cli app build -b ollama-debug -''' diff --git a/llm/anthropic/src/bindings.rs b/llm/anthropic/src/bindings.rs index 70c5f1fd5..1a54d6167 100644 --- a/llm/anthropic/src/bindings.rs +++ b/llm/anthropic/src/bindings.rs @@ -1,12 +1,15 @@ -// Generated by `wit-bindgen` 0.36.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! // Options used: // * runtime_path: "wit_bindgen_rt" // * with "golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm" // * generate_unused_types use golem_llm::golem::llm::llm as __with_name0; #[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.36.0:golem:llm-anthropic@1.0.0:llm-library:encoded world"] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:llm-anthropic@1.0.0:llm-library:encoded world" +)] #[doc(hidden)] +#[allow(clippy::octal_escapes)] pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1762] = *b"\ \0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe0\x0c\x01A\x02\x01\ A\x02\x01BO\x01m\x04\x04user\x09assistant\x06system\x04tool\x04\0\x04role\x03\0\0\ @@ -43,8 +46,8 @@ ng-get-next\x01B\x01p\x15\x01@\x02\x08messages\xc3\0\x06config)\06\x04\0\x04send \0\x06config)\06\x04\0\x08continue\x01G\x01i=\x01@\x02\x08messages\xc3\0\x06conf\ ig)\0\xc8\0\x04\0\x06stream\x01I\x04\0\x13golem:llm/llm@1.0.0\x05\0\x04\0%golem:\ llm-anthropic/llm-library@1.0.0\x04\0\x0b\x11\x01\0\x0bllm-library\x03\0\0\0G\x09\ -producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.220.0\x10wit-bindgen-rus\ -t\x060.36.0"; +producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rus\ +t\x060.41.0"; #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { diff --git a/llm/anthropic/src/conversions.rs b/llm/anthropic/src/conversions.rs index e332f1391..e7d3175a0 100644 --- a/llm/anthropic/src/conversions.rs +++ b/llm/anthropic/src/conversions.rs @@ -130,7 +130,7 @@ pub fn process_response(response: MessagesResponse) -> ChatEvent { Err(e) => { return ChatEvent::Error(Error { code: ErrorCode::InvalidRequest, - message: format!("Failed to decode base64 image data: {}", e), + message: format!("Failed to decode base64 image data: {e}"), provider_error_json: None, }); } diff --git a/llm/grok/src/bindings.rs b/llm/grok/src/bindings.rs index 2a101583e..c2f601347 100644 --- a/llm/grok/src/bindings.rs +++ b/llm/grok/src/bindings.rs @@ -1,12 +1,15 @@ -// Generated by `wit-bindgen` 0.36.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! // Options used: // * runtime_path: "wit_bindgen_rt" // * with "golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm" // * generate_unused_types use golem_llm::golem::llm::llm as __with_name0; #[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.36.0:golem:llm-grok@1.0.0:llm-library:encoded world"] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:llm-grok@1.0.0:llm-library:encoded world" +)] #[doc(hidden)] +#[allow(clippy::octal_escapes)] pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1757] = *b"\ \0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xdb\x0c\x01A\x02\x01\ A\x02\x01BO\x01m\x04\x04user\x09assistant\x06system\x04tool\x04\0\x04role\x03\0\0\ @@ -43,8 +46,8 @@ ng-get-next\x01B\x01p\x15\x01@\x02\x08messages\xc3\0\x06config)\06\x04\0\x04send \0\x06config)\06\x04\0\x08continue\x01G\x01i=\x01@\x02\x08messages\xc3\0\x06conf\ ig)\0\xc8\0\x04\0\x06stream\x01I\x04\0\x13golem:llm/llm@1.0.0\x05\0\x04\0\x20gol\ em:llm-grok/llm-library@1.0.0\x04\0\x0b\x11\x01\0\x0bllm-library\x03\0\0\0G\x09p\ -roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.220.0\x10wit-bindgen-rust\ -\x060.36.0"; +roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rust\ +\x060.41.0"; #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { diff --git a/llm/grok/src/conversions.rs b/llm/grok/src/conversions.rs index 68a5d570c..129c128ad 100644 --- a/llm/grok/src/conversions.rs +++ b/llm/grok/src/conversions.rs @@ -183,7 +183,7 @@ fn convert_content_parts(contents: Vec) -> crate::client::Content { let media_type = &image_source.mime_type; // This is already a string result.push(crate::client::ContentPart::ImageInput { image_url: crate::client::ImageUrl { - url: format!("data:{};base64,{}", media_type, base64_data), + url: format!("data:{media_type};base64,{base64_data}"), detail: image_source.detail.map(|d| d.into()), }, }); diff --git a/llm/llm/src/event_source/ndjson_stream.rs b/llm/llm/src/event_source/ndjson_stream.rs index e2f4cc1b2..1b8ef3773 100644 --- a/llm/llm/src/event_source/ndjson_stream.rs +++ b/llm/llm/src/event_source/ndjson_stream.rs @@ -126,7 +126,7 @@ fn try_parse_line( return Ok(None); } - trace!("Parsed NDJSON line: {}", line); + trace!("Parsed NDJSON line: {line}"); // Create a MessageEvent with the JSON line as data let event = MessageEvent { diff --git a/llm/llm/src/event_source/stream.rs b/llm/llm/src/event_source/stream.rs index 8f2933676..13a5eeb56 100644 --- a/llm/llm/src/event_source/stream.rs +++ b/llm/llm/src/event_source/stream.rs @@ -56,9 +56,9 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Utf8(err) => f.write_fmt(format_args!("UTF8 error: {}", err)), - Self::Parser(err) => f.write_fmt(format_args!("Parse error: {}", err)), - Self::Transport(err) => f.write_fmt(format_args!("Transport error: {}", err)), + Self::Utf8(err) => f.write_fmt(format_args!("UTF8 error: {err}")), + Self::Parser(err) => f.write_fmt(format_args!("Parse error: {err}")), + Self::Transport(err) => f.write_fmt(format_args!("Transport error: {err}")), } } } diff --git a/llm/ollama/Cargo.toml b/llm/ollama/Cargo.toml index b43c25b85..96a36d8f7 100644 --- a/llm/ollama/Cargo.toml +++ b/llm/ollama/Cargo.toml @@ -18,9 +18,9 @@ durability = ["golem-rust/durability", "golem-llm/durability"] [dependencies] golem-llm = { workspace = true } -base64 = "0.21" -mime_guess = "2.0" -url = "2.4" +base64 = { workspace = true } +mime_guess = { workspace = true } +url = { workspace = true } golem-rust = { workspace = true } log = { workspace = true } diff --git a/llm/ollama/src/bindings.rs b/llm/ollama/src/bindings.rs index dbb704704..269cd07fb 100644 --- a/llm/ollama/src/bindings.rs +++ b/llm/ollama/src/bindings.rs @@ -1,12 +1,15 @@ -// Generated by `wit-bindgen` 0.36.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! // Options used: // * runtime_path: "wit_bindgen_rt" // * with "golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm" // * generate_unused_types use golem_llm::golem::llm::llm as __with_name0; #[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.36.0:golem:llm-ollama@1.0.0:llm-library:encoded world"] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:llm-ollama@1.0.0:llm-library:encoded world" +)] #[doc(hidden)] +#[allow(clippy::octal_escapes)] pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1759] = *b"\ \0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xdd\x0c\x01A\x02\x01\ A\x02\x01BO\x01m\x04\x04user\x09assistant\x06system\x04tool\x04\0\x04role\x03\0\0\ @@ -43,8 +46,8 @@ ng-get-next\x01B\x01p\x15\x01@\x02\x08messages\xc3\0\x06config)\06\x04\0\x04send \0\x06config)\06\x04\0\x08continue\x01G\x01i=\x01@\x02\x08messages\xc3\0\x06conf\ ig)\0\xc8\0\x04\0\x06stream\x01I\x04\0\x13golem:llm/llm@1.0.0\x05\0\x04\0\"golem\ :llm-ollama/llm-library@1.0.0\x04\0\x0b\x11\x01\0\x0bllm-library\x03\0\0\0G\x09p\ -roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.220.0\x10wit-bindgen-rust\ -\x060.36.0"; +roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rust\ +\x060.41.0"; #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { diff --git a/llm/ollama/src/client.rs b/llm/ollama/src/client.rs index e9514a8dd..e2901e70c 100644 --- a/llm/ollama/src/client.rs +++ b/llm/ollama/src/client.rs @@ -335,7 +335,7 @@ pub fn image_to_base64(source: &str) -> Result Error { Error { code: ErrorCode::InternalError, - message: format!("{}: {}", context, err), + message: format!("{context}: {err}"), provider_error_json: None, } } diff --git a/llm/ollama/src/conversions.rs b/llm/ollama/src/conversions.rs index b1db65c61..8d64e954f 100644 --- a/llm/ollama/src/conversions.rs +++ b/llm/ollama/src/conversions.rs @@ -214,7 +214,7 @@ pub fn process_response(response: CompletionsResponse) -> ChatEvent { }; ChatEvent::Message(CompleteResponse { - id: format!("ollama-{}", timestamp), + id: format!("ollama-{timestamp}"), content, tool_calls, metadata, diff --git a/llm/openai/src/bindings.rs b/llm/openai/src/bindings.rs index c960248a8..6d0a77280 100644 --- a/llm/openai/src/bindings.rs +++ b/llm/openai/src/bindings.rs @@ -1,12 +1,15 @@ -// Generated by `wit-bindgen` 0.36.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! // Options used: // * runtime_path: "wit_bindgen_rt" // * with "golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm" // * generate_unused_types use golem_llm::golem::llm::llm as __with_name0; #[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.36.0:golem:llm-openai@1.0.0:llm-library:encoded world"] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:llm-openai@1.0.0:llm-library:encoded world" +)] #[doc(hidden)] +#[allow(clippy::octal_escapes)] pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1759] = *b"\ \0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xdd\x0c\x01A\x02\x01\ A\x02\x01BO\x01m\x04\x04user\x09assistant\x06system\x04tool\x04\0\x04role\x03\0\0\ @@ -43,8 +46,8 @@ ng-get-next\x01B\x01p\x15\x01@\x02\x08messages\xc3\0\x06config)\06\x04\0\x04send \0\x06config)\06\x04\0\x08continue\x01G\x01i=\x01@\x02\x08messages\xc3\0\x06conf\ ig)\0\xc8\0\x04\0\x06stream\x01I\x04\0\x13golem:llm/llm@1.0.0\x05\0\x04\0\"golem\ :llm-openai/llm-library@1.0.0\x04\0\x0b\x11\x01\0\x0bllm-library\x03\0\0\0G\x09p\ -roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.220.0\x10wit-bindgen-rust\ -\x060.36.0"; +roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rust\ +\x060.41.0"; #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { diff --git a/llm/openai/src/conversions.rs b/llm/openai/src/conversions.rs index 43694c0f3..a4989b0c1 100644 --- a/llm/openai/src/conversions.rs +++ b/llm/openai/src/conversions.rs @@ -138,7 +138,7 @@ pub fn content_part_to_inner_input_item(content_part: ContentPart) -> InnerInput ImageReference::Inline(image_source) => { let base64_data = general_purpose::STANDARD.encode(&image_source.data); let mime_type = &image_source.mime_type; // This is already a string - let data_url = format!("data:{};base64,{}", mime_type, base64_data); + let data_url = format!("data:{mime_type};base64,{base64_data}"); InnerInputItem::ImageInput { image_url: data_url, diff --git a/llm/openrouter/src/bindings.rs b/llm/openrouter/src/bindings.rs index ba2accf7e..1300cde97 100644 --- a/llm/openrouter/src/bindings.rs +++ b/llm/openrouter/src/bindings.rs @@ -1,12 +1,15 @@ -// Generated by `wit-bindgen` 0.36.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! // Options used: // * runtime_path: "wit_bindgen_rt" // * with "golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm" // * generate_unused_types use golem_llm::golem::llm::llm as __with_name0; #[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.36.0:golem:llm-openrouter@1.0.0:llm-library:encoded world"] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:llm-openrouter@1.0.0:llm-library:encoded world" +)] #[doc(hidden)] +#[allow(clippy::octal_escapes)] pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1763] = *b"\ \0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe1\x0c\x01A\x02\x01\ A\x02\x01BO\x01m\x04\x04user\x09assistant\x06system\x04tool\x04\0\x04role\x03\0\0\ @@ -43,8 +46,8 @@ ng-get-next\x01B\x01p\x15\x01@\x02\x08messages\xc3\0\x06config)\06\x04\0\x04send \0\x06config)\06\x04\0\x08continue\x01G\x01i=\x01@\x02\x08messages\xc3\0\x06conf\ ig)\0\xc8\0\x04\0\x06stream\x01I\x04\0\x13golem:llm/llm@1.0.0\x05\0\x04\0&golem:\ llm-openrouter/llm-library@1.0.0\x04\0\x0b\x11\x01\0\x0bllm-library\x03\0\0\0G\x09\ -producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.220.0\x10wit-bindgen-rus\ -t\x060.36.0"; +producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rus\ +t\x060.41.0"; #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { diff --git a/llm/openrouter/src/conversions.rs b/llm/openrouter/src/conversions.rs index d4db2d34c..61b5f973b 100644 --- a/llm/openrouter/src/conversions.rs +++ b/llm/openrouter/src/conversions.rs @@ -184,7 +184,7 @@ fn convert_content_parts(contents: Vec) -> crate::client::Content { let media_type = &image_source.mime_type; // This is already a string result.push(crate::client::ContentPart::ImageInput { image_url: crate::client::ImageUrl { - url: format!("data:{};base64,{}", media_type, base64_data), + url: format!("data:{media_type};base64,{base64_data}"), detail: image_source.detail.map(|d| d.into()), }, }); diff --git a/test/components-rust/.gitignore b/test/components-rust/.gitignore deleted file mode 100644 index f19eeb7b2..000000000 --- a/test/components-rust/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/*/src/bindings.rs -/*/wit-generated \ No newline at end of file diff --git a/test/.gitignore b/test/llm/.gitignore similarity index 100% rename from test/.gitignore rename to test/llm/.gitignore diff --git a/test/.vscode/settings.json b/test/llm/.vscode/settings.json similarity index 100% rename from test/.vscode/settings.json rename to test/llm/.vscode/settings.json diff --git a/test/Cargo.lock b/test/llm/Cargo.lock similarity index 100% rename from test/Cargo.lock rename to test/llm/Cargo.lock diff --git a/test/Cargo.toml b/test/llm/Cargo.toml similarity index 100% rename from test/Cargo.toml rename to test/llm/Cargo.toml diff --git a/test/common-rust/common-lib/Cargo.toml b/test/llm/common-rust/common-lib/Cargo.toml similarity index 100% rename from test/common-rust/common-lib/Cargo.toml rename to test/llm/common-rust/common-lib/Cargo.toml diff --git a/test/common-rust/common-lib/src/lib.rs b/test/llm/common-rust/common-lib/src/lib.rs similarity index 100% rename from test/common-rust/common-lib/src/lib.rs rename to test/llm/common-rust/common-lib/src/lib.rs diff --git a/test/common-rust/golem.yaml b/test/llm/common-rust/golem.yaml similarity index 100% rename from test/common-rust/golem.yaml rename to test/llm/common-rust/golem.yaml diff --git a/test/llm/components-rust/.gitignore b/test/llm/components-rust/.gitignore new file mode 100644 index 000000000..19195863f --- /dev/null +++ b/test/llm/components-rust/.gitignore @@ -0,0 +1,3 @@ +/*/src/bindings.rs +/*/wit-generated +/*/golem-temp \ No newline at end of file diff --git a/test/components-rust/test-helper/Cargo.lock b/test/llm/components-rust/test-helper/Cargo.lock similarity index 100% rename from test/components-rust/test-helper/Cargo.lock rename to test/llm/components-rust/test-helper/Cargo.lock diff --git a/test/components-rust/test-helper/Cargo.toml b/test/llm/components-rust/test-helper/Cargo.toml similarity index 100% rename from test/components-rust/test-helper/Cargo.toml rename to test/llm/components-rust/test-helper/Cargo.toml diff --git a/test/components-rust/test-helper/golem.yaml b/test/llm/components-rust/test-helper/golem.yaml similarity index 100% rename from test/components-rust/test-helper/golem.yaml rename to test/llm/components-rust/test-helper/golem.yaml diff --git a/test/components-rust/test-helper/src/lib.rs b/test/llm/components-rust/test-helper/src/lib.rs similarity index 100% rename from test/components-rust/test-helper/src/lib.rs rename to test/llm/components-rust/test-helper/src/lib.rs diff --git a/test/components-rust/test-helper/wit/test-helper.wit b/test/llm/components-rust/test-helper/wit/test-helper.wit similarity index 100% rename from test/components-rust/test-helper/wit/test-helper.wit rename to test/llm/components-rust/test-helper/wit/test-helper.wit diff --git a/test/components-rust/test-llm/Cargo.lock b/test/llm/components-rust/test-llm/Cargo.lock similarity index 100% rename from test/components-rust/test-llm/Cargo.lock rename to test/llm/components-rust/test-llm/Cargo.lock diff --git a/test/components-rust/test-llm/Cargo.toml b/test/llm/components-rust/test-llm/Cargo.toml similarity index 100% rename from test/components-rust/test-llm/Cargo.toml rename to test/llm/components-rust/test-llm/Cargo.toml index 7f6242874..ca8b9eb72 100644 --- a/test/components-rust/test-llm/Cargo.toml +++ b/test/llm/components-rust/test-llm/Cargo.toml @@ -37,8 +37,8 @@ path = "wit-generated" [package.metadata.component.target.dependencies] "golem:llm" = { path = "wit-generated/deps/golem-llm" } -"wasi:clocks" = { path = "wit-generated/deps/clocks" } "wasi:io" = { path = "wit-generated/deps/io" } +"wasi:clocks" = { path = "wit-generated/deps/clocks" } "golem:rpc" = { path = "wit-generated/deps/golem-rpc" } "test:helper-client" = { path = "wit-generated/deps/test_helper-client" } "test:llm-exports" = { path = "wit-generated/deps/test_llm-exports" } diff --git a/test/components-rust/test-llm/golem.yaml b/test/llm/components-rust/test-llm/golem.yaml similarity index 76% rename from test/components-rust/test-llm/golem.yaml rename to test/llm/components-rust/test-llm/golem.yaml index 6efa177c7..e302a764c 100644 --- a/test/components-rust/test-llm/golem.yaml +++ b/test/llm/components-rust/test-llm/golem.yaml @@ -22,10 +22,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/debug/golem_llm_openai.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_openai_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_llm_openai.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_openai_plugged.wasm sources: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - ../../../target/wasm32-wasip1/debug/golem_llm_openai.wasm + - ../../../../target/wasm32-wasip1/debug/golem_llm_openai.wasm targets: - ../../target/wasm32-wasip1/debug/test_openai_plugged.wasm sourceWit: wit @@ -48,10 +48,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/debug/golem_llm_anthropic.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_anthropic_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_llm_anthropic.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_anthropic_plugged.wasm sources: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - ../../../target/wasm32-wasip1/debug/golem_llm_anthropic.wasm + - ../../../../target/wasm32-wasip1/debug/golem_llm_anthropic.wasm targets: - ../../target/wasm32-wasip1/debug/test_anthropic_plugged.wasm sourceWit: wit @@ -74,10 +74,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/debug/golem_llm_grok.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_grok_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_llm_grok.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_grok_plugged.wasm sources: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - ../../../target/wasm32-wasip1/debug/golem_llm_grok.wasm + - ../../../../target/wasm32-wasip1/debug/golem_llm_grok.wasm targets: - ../../target/wasm32-wasip1/debug/test_grok_plugged.wasm sourceWit: wit @@ -100,10 +100,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/debug/golem_llm_openrouter.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_openrouter_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_llm_openrouter.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_openrouter_plugged.wasm sources: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - ../../../target/wasm32-wasip1/debug/golem_llm_openrouter.wasm + - ../../../../target/wasm32-wasip1/debug/golem_llm_openrouter.wasm targets: - ../../target/wasm32-wasip1/debug/test_openrouter_plugged.wasm sourceWit: wit @@ -126,10 +126,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/debug/golem_llm_ollama.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_ollama_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_llm_ollama.wasm ../../target/wasm32-wasip1/debug/test_llm.wasm -o ../../target/wasm32-wasip1/debug/test_ollama_plugged.wasm sources: - ../../target/wasm32-wasip1/debug/test_llm.wasm - - ../../../target/wasm32-wasip1/debug/golem_llm_ollama.wasm + - ../../../../target/wasm32-wasip1/debug/golem_llm_ollama.wasm targets: - ../../target/wasm32-wasip1/debug/test_ollama_plugged.wasm sourceWit: wit @@ -153,10 +153,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/release/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/release/golem_llm_openai.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_openai_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_llm_openai.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_openai_plugged.wasm sources: - ../../target/wasm32-wasip1/release/test_llm.wasm - - ../../../target/wasm32-wasip1/release/golem_llm_openai.wasm + - ../../../../target/wasm32-wasip1/release/golem_llm_openai.wasm targets: - ../../target/wasm32-wasip1/release/test_openai_plugged.wasm sourceWit: wit @@ -179,10 +179,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/release/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/release/golem_llm_anthropic.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_anthropic_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_llm_anthropic.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_anthropic_plugged.wasm sources: - ../../target/wasm32-wasip1/release/test_llm.wasm - - ../../../target/wasm32-wasip1/release/golem_llm_anthropic.wasm + - ../../../../target/wasm32-wasip1/release/golem_llm_anthropic.wasm targets: - ../../target/wasm32-wasip1/release/test_anthropic_plugged.wasm sourceWit: wit @@ -205,10 +205,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/release/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/release/golem_llm_grok.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_grok_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_llm_grok.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_grok_plugged.wasm sources: - ../../target/wasm32-wasip1/release/test_llm.wasm - - ../../../target/wasm32-wasip1/release/golem_llm_grok.wasm + - ../../../../target/wasm32-wasip1/release/golem_llm_grok.wasm targets: - ../../target/wasm32-wasip1/release/test_grok_plugged.wasm sourceWit: wit @@ -231,10 +231,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/release/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/release/golem_llm_openrouter.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_openrouter_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_llm_openrouter.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_openrouter_plugged.wasm sources: - ../../target/wasm32-wasip1/release/test_llm.wasm - - ../../../target/wasm32-wasip1/release/golem_llm_openrouter.wasm + - ../../../../target/wasm32-wasip1/release/golem_llm_openrouter.wasm targets: - ../../target/wasm32-wasip1/release/test_openrouter_plugged.wasm sourceWit: wit @@ -257,10 +257,10 @@ components: - ../../common-rust targets: - ../../target/wasm32-wasip1/release/test_llm.wasm - - command: wac plug --plug ../../../target/wasm32-wasip1/release/golem_llm_ollama.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_ollama_plugged.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_llm_ollama.wasm ../../target/wasm32-wasip1/release/test_llm.wasm -o ../../target/wasm32-wasip1/release/test_ollama_plugged.wasm sources: - ../../target/wasm32-wasip1/release/test_llm.wasm - - ../../../target/wasm32-wasip1/release/golem_llm_ollama.wasm + - ../../../../target/wasm32-wasip1/release/golem_llm_ollama.wasm targets: - ../../target/wasm32-wasip1/release/test_ollama_plugged.wasm sourceWit: wit diff --git a/test/components-rust/test-llm/src/lib.rs b/test/llm/components-rust/test-llm/src/lib.rs similarity index 100% rename from test/components-rust/test-llm/src/lib.rs rename to test/llm/components-rust/test-llm/src/lib.rs diff --git a/test/components-rust/test-llm/wit/test-llm.wit b/test/llm/components-rust/test-llm/wit/test-llm.wit similarity index 100% rename from test/components-rust/test-llm/wit/test-llm.wit rename to test/llm/components-rust/test-llm/wit/test-llm.wit diff --git a/test/data/cat.png b/test/llm/data/cat.png similarity index 100% rename from test/data/cat.png rename to test/llm/data/cat.png diff --git a/test/golem.yaml b/test/llm/golem.yaml similarity index 100% rename from test/golem.yaml rename to test/llm/golem.yaml diff --git a/test/wit/deps/golem-llm/golem-llm.wit b/test/llm/wit/deps/golem-llm/golem-llm.wit similarity index 100% rename from test/wit/deps/golem-llm/golem-llm.wit rename to test/llm/wit/deps/golem-llm/golem-llm.wit diff --git a/test/wit/deps/io/error.wit b/test/llm/wit/deps/io/error.wit similarity index 100% rename from test/wit/deps/io/error.wit rename to test/llm/wit/deps/io/error.wit diff --git a/test/wit/deps/io/poll.wit b/test/llm/wit/deps/io/poll.wit similarity index 100% rename from test/wit/deps/io/poll.wit rename to test/llm/wit/deps/io/poll.wit diff --git a/test/wit/deps/io/streams.wit b/test/llm/wit/deps/io/streams.wit similarity index 100% rename from test/wit/deps/io/streams.wit rename to test/llm/wit/deps/io/streams.wit diff --git a/test/wit/deps/io/world.wit b/test/llm/wit/deps/io/world.wit similarity index 100% rename from test/wit/deps/io/world.wit rename to test/llm/wit/deps/io/world.wit diff --git a/test/video-advanced/.gitignore b/test/video-advanced/.gitignore new file mode 100644 index 000000000..ed09b5399 --- /dev/null +++ b/test/video-advanced/.gitignore @@ -0,0 +1,2 @@ +/golem-temp +/target diff --git a/test/video-advanced/.vscode/settings.json b/test/video-advanced/.vscode/settings.json new file mode 100644 index 000000000..7530c05d7 --- /dev/null +++ b/test/video-advanced/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.server.extraEnv": { "CARGO": "cargo-component" } +} \ No newline at end of file diff --git a/test/video-advanced/Cargo.lock b/test/video-advanced/Cargo.lock new file mode 100644 index 000000000..949de9d9c --- /dev/null +++ b/test/video-advanced/Cargo.lock @@ -0,0 +1,29 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "common-lib" +version = "0.1.0" + +[[package]] +name = "test_video_advanced" +version = "0.0.1" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68faed92ae696b93ea9a7b67ba6c37bf09d72c6d9a70fa824a743c3020212f11" +dependencies = [ + "bitflags", +] diff --git a/test/video-advanced/Cargo.toml b/test/video-advanced/Cargo.toml new file mode 100644 index 000000000..ae41bab4c --- /dev/null +++ b/test/video-advanced/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +resolver = "2" +members = ["components-rust/*", "common-rust/*"] + +[profile.release] +opt-level = "s" +lto = true + +[workspace.dependencies] +golem-rust = { version = "1.6.0", features = [ + "export_load_snapshot", + "export_save_snapshot", + "export_oplog_processor", +] } +reqwest = { git = "https://github.com/zivergetech/reqwest", branch = "update-may-2025", features = [ + "json", +] } +serde = { version = "1.0.0", features = ["derive"] } +serde_json = "1.0" +wit-bindgen-rt = { version = "0.40.0", features = ["bitflags"] } diff --git a/test/video-advanced/common-rust/common-lib/Cargo.toml b/test/video-advanced/common-rust/common-lib/Cargo.toml new file mode 100644 index 000000000..876f1d66d --- /dev/null +++ b/test/video-advanced/common-rust/common-lib/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "common-lib" +version = "0.1.0" +edition = "2021" diff --git a/test/video-advanced/common-rust/common-lib/src/lib.rs b/test/video-advanced/common-rust/common-lib/src/lib.rs new file mode 100644 index 000000000..15c6dcba7 --- /dev/null +++ b/test/video-advanced/common-rust/common-lib/src/lib.rs @@ -0,0 +1,3 @@ +pub fn example_common_function() -> &'static str { + "hello common" +} diff --git a/test/video-advanced/common-rust/golem.yaml b/test/video-advanced/common-rust/golem.yaml new file mode 100644 index 000000000..5d65c9ff5 --- /dev/null +++ b/test/video-advanced/common-rust/golem.yaml @@ -0,0 +1,47 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference +# For creating APIs see https://learn.golem.cloud/invoke/making-custom-apis + +templates: + rust: + profiles: + debug: + build: + - command: cargo component build + sources: + - src + - wit-generated + - ../../common-rust + - Cargo.toml + targets: + - ../../target/wasm32-wasip1/debug/{{ component_name | to_snake_case }}.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/{{ component_name | to_snake_case }}.wasm + linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}_debug.wasm + clean: + - src/bindings.rs + release: + build: + - command: cargo component build --release + sources: + - src + - wit-generated + - ../../common-rust + - Cargo.toml + targets: + - ../../target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm + linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}_release.wasm + clean: + - src/bindings.rs + defaultProfile: debug +customCommands: + cargo-clean: + - command: cargo clean diff --git a/test/video-advanced/components-rust/.gitignore b/test/video-advanced/components-rust/.gitignore new file mode 100644 index 000000000..da30a67a6 --- /dev/null +++ b/test/video-advanced/components-rust/.gitignore @@ -0,0 +1,3 @@ +/*/src/bindings.rs +/*/wit-generated +/*/golem-temp diff --git a/test/video-advanced/components-rust/test-video-advanced/Cargo.lock b/test/video-advanced/components-rust/test-video-advanced/Cargo.lock new file mode 100644 index 000000000..4632cbebd --- /dev/null +++ b/test/video-advanced/components-rust/test-video-advanced/Cargo.lock @@ -0,0 +1,1376 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "test-video-advanced" +version = "0.0.1" +dependencies = [ + "golem-rust", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "golem-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c967eb388fb81f9b9f4df5d5b6634de803f21cd410c1bf687202794a4fbc0267" +dependencies = [ + "golem-rust-macro", + "serde", + "serde_json", + "uuid", + "wit-bindgen-rt", +] + +[[package]] +name = "golem-rust-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb87f831cfe4371427c63f5f4cabcc3bae1b66974c8fbcf22be9274fee3a7d1" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[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 = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "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", +] + +[[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 = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "git+https://github.com/zivergetech/reqwest?branch=update-jun-2024#1cf59c67b93aa6292961f8948b93df5bca2753b6" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", + "wit-bindgen-rt", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[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-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/test/video-advanced/components-rust/test-video-advanced/Cargo.toml b/test/video-advanced/components-rust/test-video-advanced/Cargo.toml new file mode 100644 index 000000000..12f533723 --- /dev/null +++ b/test/video-advanced/components-rust/test-video-advanced/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "test_video_advanced" +version = "0.0.1" +edition = "2021" + +[features] +default = ["kling"] +kling = [] + +[package.metadata] + +[package.metadata.component] + +[package.metadata.component.target] +path = "wit-generated" + +[package.metadata.component.target.dependencies] +"golem:video" = { path = "wit-generated/deps/golem-video" } +"test:video-advanced-exports" = { path = "wit-generated/deps/test_video-advanced-exports" } + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] +required-features = [] + +[dependencies] +wit-bindgen-rt = { workspace = true } diff --git a/test/video-advanced/components-rust/test-video-advanced/golem.yaml b/test/video-advanced/components-rust/test-video-advanced/golem.yaml new file mode 100644 index 000000000..fd4e99fd2 --- /dev/null +++ b/test/video-advanced/components-rust/test-video-advanced/golem.yaml @@ -0,0 +1,108 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference +# For creating APIs see https://learn.golem.cloud/invoke/making-custom-apis + +components: + test:video-advanced: + profiles: + # DEBUG PROFILES + kling-debug: + files: + - sourcePath: ../../data/first.png + targetPath: /data/first.png + permissions: read-only + - sourcePath: ../../data/last.png + targetPath: /data/last.png + permissions: read-only + - sourcePath: ../../data/cameracontrol.jpeg + targetPath: /data/cameracontrol.jpeg + permissions: read-only + - sourcePath: ../../data/single-effect.jpeg + targetPath: /data/single-effect.jpeg + permissions: read-only + - sourcePath: ../../data/multi-image.jpeg + targetPath: /data/multi-image.jpeg + permissions: read-only + - sourcePath: ../../data/audio.wav + targetPath: /data/audio.wav + permissions: read-only + - sourcePath: ../../data/lipsync.mp4 + targetPath: /data/lipsync.mp4 + permissions: read-only + build: + - command: cargo component build --no-default-features --features kling + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/debug/test_video_advanced.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_video_kling.wasm ../../target/wasm32-wasip1/debug/test_video_advanced.wasm -o ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/debug/test_video_advanced.wasm + - ../../../../target/wasm32-wasip1/debug/golem_video_kling.wasm + targets: + - ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_kling_debug.wasm + clean: + - src/bindings.rs + # RELEASE PROFILES + kling-release: + files: + - sourcePath: ../../data/first.png + targetPath: /data/first.png + permissions: read-only + - sourcePath: ../../data/last.png + targetPath: /data/last.png + permissions: read-only + - sourcePath: ../../data/cameracontrol.jpeg + targetPath: /data/cameracontrol.jpeg + permissions: read-only + - sourcePath: ../../data/single-effect.jpeg + targetPath: /data/single-effect.jpeg + permissions: read-only + - sourcePath: ../../data/multi-image.jpeg + targetPath: /data/multi-image.jpeg + permissions: read-only + - sourcePath: ../../data/audio.wav + targetPath: /data/audio.wav + permissions: read-only + - sourcePath: ../../data/lipsync.mp4 + targetPath: /data/lipsync.mp4 + permissions: read-only + build: + - command: cargo component build --release --no-default-features --features kling + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/release/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_video_kling.wasm ../../target/wasm32-wasip1/release/test_video.wasm -o ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/release/test_video.wasm + - ../../../../target/wasm32-wasip1/release/golem_video_kling.wasm + targets: + - ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_kling_release.wasm + clean: + - src/bindings.rs + defaultProfile: kling-debug + +# Example for adding dependencies for Worker to Worker communication: +# See https://learn.golem.cloud/docs/app-manifest#fields_dependencies for more information +# +#dependencies: +# test:video-advanced: +# - target: +# type: wasm-rpc diff --git a/test/video-advanced/components-rust/test-video-advanced/src/lib.rs b/test/video-advanced/components-rust/test-video-advanced/src/lib.rs new file mode 100644 index 000000000..4b913bfb1 --- /dev/null +++ b/test/video-advanced/components-rust/test-video-advanced/src/lib.rs @@ -0,0 +1,551 @@ +#[allow(static_mut_refs)] +mod bindings; + +use crate::bindings::exports::test::video_advanced_exports::test_video_api::*; +use crate::bindings::golem::video::types; +use crate::bindings::golem::video::{video_generation, advanced, lip_sync}; +use std::fs; +use std::fs::File; +use std::io::Read; +use std::thread; +use std::time::Duration; +use std::sync::Mutex; + +static JOB_ID_STORAGE: Mutex = Mutex::new(String::new()); + +struct Component; + +impl Guest for Component { + + /// Test1 - Image to video generation with first frame and last frame included (both inline images) + fn test1() -> String { + println!("Test1: Image to video with first frame and last frame"); + + // Load test image for both first and last frame + let (first_image_bytes, first_image_mime_type) = match load_file_bytes("/data/first.png") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR: {}", err), + }; + + let (last_image_bytes, last_image_mime_type) = match load_file_bytes("/data/last.png") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR: {}", err), + }; + + // Create configuration with lastframe + let config = types::GenerationConfig { + negative_prompt: Some("blurry, low quality, distorted".to_string()), + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: Some(types::AspectRatio::Square), + model: None, + duration_seconds: Some(5.0), + resolution: Some(types::Resolution::Hd), + enable_audio: Some(false), + enhance_prompt: Some(true), + provider_options: None, + lastframe: Some(types::InputImage { + data: types::MediaData::Bytes(types::RawBytes { + bytes: last_image_bytes.clone(), + mime_type: last_image_mime_type.clone(), + }), + }), + static_mask: None, + dynamic_mask: None, + camera_control: None, + }; + + // Create media input with first frame image + let media_input = types::MediaInput::Image(types::Reference { + data: types::InputImage { + data: types::MediaData::Bytes(types::RawBytes { + bytes: first_image_bytes, + mime_type: first_image_mime_type, + }), + }, + prompt: Some("A close up shot of eagle that slowly zooms into its eyes, and then it zooms out to a headshot of a majestic lion, smooth camera movement" .to_string()), + role: Some(types::ImageRole::First), + }); + + println!("Sending first/last frame video generation request..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test1") + } + + /// Test2 - Image to video generation with advancedcamera control enum + fn test2() -> String { + println!("Test2: Image to video with advancedcamera control enum"); + + // Load test image + let (image_bytes, image_mime_type) = match load_file_bytes("/data/cameracontrol.jpeg") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR: {}", err), + }; + + // Create configuration with camera movement enum + let config = types::GenerationConfig { + negative_prompt: Some("static, boring, low quality".to_string()), + seed: None, + scheduler: None, + guidance_scale: Some(7.5), + aspect_ratio: Some(types::AspectRatio::Square), + model: None, + duration_seconds: Some(5.0), + resolution: Some(types::Resolution::Fhd), + enable_audio: Some(false), + enhance_prompt: Some(true), + provider_options: None, + lastframe: None, + static_mask: None, + dynamic_mask: None, + camera_control: Some(types::CameraControl::Movement(types::CameraMovement::ForwardUp)), + }; + + let media_input = types::MediaInput::Image(types::Reference { + data: types::InputImage { + data: types::MediaData::Bytes(types::RawBytes { + bytes: image_bytes, + mime_type: image_mime_type, + }), + }, + prompt: Some("The scally dragon slowly breaths embers and smoke, it eyes glow and spark, the flame make the dragon light up".to_string()), + role: None, + }); + + println!("Sending image-to-video with camera control request..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test2") + } + + /// Test3 - Image to video generation with static and dynamic mask (URL input, save job-id for test9) + fn test3() -> String { + println!("Test3: Image to video with static and dynamic mask"); + + // Create static mask using URL + let static_mask = types::StaticMask { + mask: types::InputImage { + data: types::MediaData::Url("https://h2.inkwai.com/bs2/upload-ylab-stunt/ai_portal/1732888177/cOLNrShrSO/static_mask.png".to_string()), + }, + }; + + // Create dynamic mask with trajectory points and URL + let dynamic_mask = types::DynamicMask { + mask: types::InputImage { + data: types::MediaData::Url("https://h2.inkwai.com/bs2/upload-ylab-stunt/ai_portal/1732888130/WU8spl23dA/dynamic_mask_1.png".to_string()), + }, + trajectories: vec![ + types::Position { x: 279, y: 219 }, + types::Position { x: 417, y: 65 }, + ], + }; + + let config = types::GenerationConfig { + negative_prompt: None, + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: Some(types::AspectRatio::Landscape), + model: None, + duration_seconds: Some(5.0), + resolution: Some(types::Resolution::Hd), + enable_audio: Some(false), + enhance_prompt: Some(false), + provider_options: None, + lastframe: None, + static_mask: Some(static_mask), + dynamic_mask: Some(dynamic_mask), + camera_control: None, + }; + + let media_input = types::MediaInput::Image(types::Reference { + + data: types::InputImage { + data: types::MediaData::Url("https://h2.inkwai.com/bs2/upload-ylab-stunt/se/ai_portal_queue_mmu_image_upscale_aiweb/3214b798-e1b4-4b00-b7af-72b5b0417420_raw_image_0.jpg".to_string()), + }, + + prompt: Some("The astronaut stood up and walked away".to_string()), + role: None, + }); + + println!("Sending static and dynamic mask video generation request..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + // Store the job ID in the Mutex + *JOB_ID_STORAGE.lock().unwrap() = job_id.clone(); + println!("Job ID for test9: {}", job_id); + poll_job_until_complete(&job_id, "test3") + } + + /// Test4 - List voice IDs and their information + fn test4() -> String { + println!("Test4: List voice IDs"); + + // List all available voices + match lip_sync::list_voices(None) { + Ok(voices) => { + let mut result = String::new(); + result.push_str("Available voices:\n"); + + for voice in voices { + result.push_str(&format!( + "Voice ID: {}, Name: {}, Language: {:?}\n", + voice.voice_id, voice.name, voice.language + )); + } + + result + } + Err(error) => { + format!("ERROR: Failed to list voices: {:?}", error) + } + } + } + + /// Test5 - Lip-sync video generation using voice-id (inline raw bytes video input) + fn test5() -> String { + println!("Test5: Lip-sync with voice-id"); + + // Load base video + // Ajay fill this in, add the video and edit cargo.toml + let (video_bytes, video_mime_type) = match load_file_bytes("/data/lipsync.mp4") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR loading video: {}", err), + }; + + let base_video = types::BaseVideo { + data: types::MediaData::Bytes(types::RawBytes { + bytes: video_bytes, + mime_type: video_mime_type, + }), + }; + + let text_to_speech = types::TextToSpeech { + text: "Hello, this is a test of Lip Sync functionality in golem video".to_string(), + voice_id: "genshin_vindi2".to_string(), + language: types::VoiceLanguage::En, + speed: 100, + }; + + let audio_source = types::AudioSource::FromText(text_to_speech); + + println!("Sending lip-sync with voice-id request..."); + let job_id = match lip_sync::generate_lip_sync(&base_video, &audio_source) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate lip-sync: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test5") + } + + /// Test6 - Lip-sync video generation using audio file (inline raw bytes audio input) + fn test6() -> String { + println!("Test6: Lip-sync with audio file"); + + // Load base video and audio file + let (video_bytes, video_mime_type) = match load_file_bytes("/data/lipsync.mp4") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR loading video: {}", err), + }; + + let (audio_bytes, audio_mime_type) = match load_file_bytes("/data/audio.wav") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR loading audio: {}", err), + }; + + let base_video = types::BaseVideo { + data: types::MediaData::Bytes(types::RawBytes { + bytes: video_bytes, + mime_type: video_mime_type, + }), + }; + + let audio_source = types::AudioSource::FromAudio(types::Narration { + data: types::MediaData::Bytes(types::RawBytes { + bytes: audio_bytes, + mime_type: audio_mime_type, + }), + }); + + println!("Sending lip-sync with audio file request..."); + let job_id = match lip_sync::generate_lip_sync(&base_video, &audio_source) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate lip-sync: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test6") + } + + /// Test7 - Video effects with single input image (inline raw bytes) amd effect boom + fn test7() -> String { + println!("Test7: Video effects with single image"); + + let (image_bytes, image_mime_type) = match load_file_bytes("/data/single-effect.jpeg") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR: {}", err), + }; + + let input_image = types::InputImage { + data: types::MediaData::Bytes(types::RawBytes { + bytes: image_bytes, + mime_type: image_mime_type, + }), + }; + + let effect = types::EffectType::Single(types::SingleImageEffects::Fuzzyfuzzy); + + println!("Sending single image effect request..."); + let job_id = match advanced::generate_video_effects( + &input_image, + &effect, + None, + None, + None, + ) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video effects: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test7") + } + + /// Test8 - Video effects with two input images (URLs) and effect "hug" + fn test8() -> String { + println!("Test8: Video effects with two images"); + + let input_image = types::InputImage { + data: types::MediaData::Url("https://p2-kling.klingai.com/bs2/upload-ylab-stunt/c54e463c95816d959602f1f2541c62b2.png".to_string()), + }; + + let second_image = types::InputImage { + data: types::MediaData::Url("https://p2-kling.klingai.com/bs2/upload-ylab-stunt/5eef15e03a70e1fa80732808a2f50f3f.png".to_string()), + }; + + let dual_effect = types::DualEffect { + effect: types::DualImageEffects::Hug, + second_image, + }; + + let effect = types::EffectType::Dual(dual_effect); + + println!("Sending dual image effect request..."); + let job_id = match advanced::generate_video_effects( + &input_image, + &effect, + None, + None, + None, + ) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video effects: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test8") + } + + /// Test9 - Extend video using job-id from test3 + /// Pre-requisite: Test3 must be run first and job-id must be stored in JOB_ID_STORAGE + fn test9() -> String { + println!("Test9: Extend video using job-id from test3"); + + // Retrieve the stored job ID + let job_id_from_test3 = JOB_ID_STORAGE.lock().unwrap().clone(); + if job_id_from_test3.is_empty() { + return "ERROR: No job ID stored from test3".to_string(); + } + + println!("Attempting to extend video with job ID: {}", job_id_from_test3); + + match advanced::extend_video( + &job_id_from_test3, + Some("and the astronaut continues to walk away"), + None, + None, + None, + ) { + Ok(extend_job_id) => { + let extend_job_id = extend_job_id.trim().to_string(); + poll_job_until_complete(&extend_job_id, "test9") + } + Err(error) => { + format!("ERROR: Failed to extend video: {:?}", error) + } + } + } + + // Test 10 - Multi-image generation (2 URLs + 1 inline raw bytes), Supports max of 4 images + fn test10() -> String { + println!("Test10: Multi-image generation (2 URLs + 1 inline raw bytes)"); + + // Load one image as inline bytes + let (image_bytes, image_mime_type) = match load_file_bytes("/data/multi-image.jpeg") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR: {}", err), + }; + + // Create a list of 3 images: 2 URLs and 1 inline bytes as specified + let input_images = vec![ + // First image - URL + types::InputImage { + data: types::MediaData::Url("https://h2.inkwai.com/bs2/upload-ylab-stunt/se/ai_portal_queue_mmu_image_upscale_aiweb/3214b798-e1b4-4b00-b7af-72b5b0417420_raw_image_0.jpg".to_string()), + }, + // Second image - URL + types::InputImage { + data: types::MediaData::Url("https://p1-kling.klingai.com/kcdn/cdn-kcdn112452/kling-api-document/multi-image-unicorn.jpeg".to_string()), + }, + // Third image - inline raw bytes + types::InputImage { + data: types::MediaData::Bytes(types::RawBytes { + bytes: image_bytes, + mime_type: image_mime_type, + }), + }, + ]; + + let config = types::GenerationConfig { + negative_prompt: None, + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: Some(types::AspectRatio::Landscape), + model: None, + duration_seconds: Some(5.0), + resolution: Some(types::Resolution::Fhd), + enable_audio: Some(false), + enhance_prompt: Some(true), + provider_options: None, + lastframe: None, + static_mask: None, + dynamic_mask: None, + camera_control: None, + }; + + let prompt: Option<&str> = Some("A girl riding a unicorn in the forest, cinematic realism style"); + + println!("Sending multi-image generation request..."); + let job_id = match advanced::multi_image_generation(&input_images, prompt, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate multi-image video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test10") + } + + +} + +// Helper function to save video result +fn save_video_result(video_result: &types::VideoResult, test_name: &str) -> String { + if let Some(videos) = &video_result.videos { + for (i, video_data) in videos.iter().enumerate() { + let filename = format!("/output/video-{}-{}.mp4", test_name, i); + + // Create output directory if it doesn't exist + if let Err(err) = fs::create_dir_all("/output") { + return format!("Failed to create output directory: {}", err); + } + + // Save the video data + match &video_data.base64_bytes { + Some(video_bytes) => { + match fs::write(&filename, video_bytes) { + Ok(_) => { + return filename; + } + Err(err) => { + return format!("Failed to save video to {}: {}", filename, err); + } + } + } + None => { + if let Some(uri) = &video_data.uri { + return format!("Video available at URI: {}", uri); + } else { + return "No video data or URI available".to_string(); + } + } + } + } + "No videos in result".to_string() + } else { + "No videos in result".to_string() + } +} + +// Helper function to load file bytes +fn load_file_bytes(path: &str) -> Result<(Vec, String), String> { + println!("Reading file from: {}", path); + let mut file = match File::open(path) { + Ok(file) => file, + Err(err) => return Err(format!("Failed to open {}: {}", path, err)), + }; + + let mut buffer = Vec::new(); + match file.read_to_end(&mut buffer) { + Ok(_) => { + println!("Successfully read {} bytes from {}", buffer.len(), path); + let mime_type = match path.rsplit('.').next() { + Some("png") => "image/png".to_string(), + Some("mp4") => "video/mp4".to_string(), + Some("mp3") => "audio/mpeg".to_string(), + Some("jpeg") => "image/jpeg".to_string(), + Some("wav") => "audio/wav".to_string(), + _ => "application/octet-stream".to_string(), // Default or unknown + }; + Ok((buffer, mime_type)) + } + Err(err) => Err(format!("Failed to read {}: {}", path, err)), + } +} + +// Polling function happens here +fn poll_job_until_complete(job_id: &str, test_name: &str) -> String { + println!("Polling for {} results with job ID: {}", test_name, job_id); + + // Wait 5 seconds after job creation before starting polling + println!("Waiting 5 seconds for job initialization..."); + thread::sleep(Duration::from_secs(5)); + + // Poll every 20 seconds until completion (Kling generation takes few minutes) + loop { + match video_generation::poll(&job_id) { + Ok(video_result) => { + match video_result.status { + types::JobStatus::Pending => { + println!("{} is pending...", test_name); + } + types::JobStatus::Running => { + println!("{} is running...", test_name); + } + types::JobStatus::Succeeded => { + println!("{} completed successfully!", test_name); + let file_path = save_video_result(&video_result, test_name); + return format!("{} generated successfully. Saved to: {}", test_name, file_path); + } + types::JobStatus::Failed(error_msg) => { + return format!("{} failed: {}", test_name, error_msg); + } + } + } + Err(error) => { + return format!("Error polling {}: {:?}", test_name, error); + } + } + + // Wait 20 seconds before polling again + thread::sleep(Duration::from_secs(20)); + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/test/video-advanced/components-rust/test-video-advanced/wit/test-video-advanced.wit b/test/video-advanced/components-rust/test-video-advanced/wit/test-video-advanced.wit new file mode 100644 index 000000000..2d8d86991 --- /dev/null +++ b/test/video-advanced/components-rust/test-video-advanced/wit/test-video-advanced.wit @@ -0,0 +1,24 @@ +package test:video-advanced; + +// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax + +interface test-video-api { + test1: func() -> string; + test2: func() -> string; + test3: func() -> string; + test4: func() -> string; + test5: func() -> string; + test6: func() -> string; + test7: func() -> string; + test8: func() -> string; + test9: func() -> string; + test10: func() -> string; +} + +world test-video { + import golem:video/types@1.0.0; + import golem:video/video-generation@1.0.0; + import golem:video/lip-sync@1.0.0; + import golem:video/advanced@1.0.0; + export test-video-api; +} diff --git a/test/video-advanced/data/audio.wav b/test/video-advanced/data/audio.wav new file mode 100644 index 000000000..07f1b8fde Binary files /dev/null and b/test/video-advanced/data/audio.wav differ diff --git a/test/video-advanced/data/cameracontrol.jpeg b/test/video-advanced/data/cameracontrol.jpeg new file mode 100644 index 000000000..5e25a5f26 Binary files /dev/null and b/test/video-advanced/data/cameracontrol.jpeg differ diff --git a/test/video-advanced/data/first.png b/test/video-advanced/data/first.png new file mode 100644 index 000000000..43b400ab3 Binary files /dev/null and b/test/video-advanced/data/first.png differ diff --git a/test/video-advanced/data/last.png b/test/video-advanced/data/last.png new file mode 100644 index 000000000..bfa4b8944 Binary files /dev/null and b/test/video-advanced/data/last.png differ diff --git a/test/video-advanced/data/lipsync.mp4 b/test/video-advanced/data/lipsync.mp4 new file mode 100644 index 000000000..fd820fb2e Binary files /dev/null and b/test/video-advanced/data/lipsync.mp4 differ diff --git a/test/video-advanced/data/multi-image.jpeg b/test/video-advanced/data/multi-image.jpeg new file mode 100644 index 000000000..4c1e5af61 Binary files /dev/null and b/test/video-advanced/data/multi-image.jpeg differ diff --git a/test/video-advanced/data/single-effect.jpeg b/test/video-advanced/data/single-effect.jpeg new file mode 100644 index 000000000..b95844a52 Binary files /dev/null and b/test/video-advanced/data/single-effect.jpeg differ diff --git a/test/video-advanced/golem.yaml b/test/video-advanced/golem.yaml new file mode 100644 index 000000000..9b87334db --- /dev/null +++ b/test/video-advanced/golem.yaml @@ -0,0 +1,13 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference +# For creating APIs see https://learn.golem.cloud/invoke/making-custom-apis + +includes: +- common-*/golem.yaml +- components-*/*/golem.yaml +witDeps: +- wit/deps diff --git a/test/video-advanced/wit/deps/golem-video/golem-video.wit b/test/video-advanced/wit/deps/golem-video/golem-video.wit new file mode 100644 index 000000000..8a2ecf138 --- /dev/null +++ b/test/video-advanced/wit/deps/golem-video/golem-video.wit @@ -0,0 +1,256 @@ +package golem:video@1.0.0; + +interface types { + variant video-error { + invalid-input(string), + unsupported-feature(string), + quota-exceeded, + generation-failed(string), + cancelled, + internal-error(string), + } + + variant media-input { + text(string), + image(reference), + video(base-video), + } + + record reference { + data: input-image, + prompt: option, + role: option, + } + + enum image-role { + first, + last, + } + + record input-image { + data: media-data, + } + + record base-video { + data: media-data, + } + + record narration { + data: media-data, + } + + variant media-data { + url(string), + bytes(raw-bytes), + } + + record raw-bytes { + bytes: list, + mime-type: string, + } + + record static-mask { + mask: input-image, + } + + record dynamic-mask { + mask: input-image, + trajectories: list, + } + + record position { + x: u32, + y: u32, + } + + enum camera-movement { + simple, + down-back, + forward-up, + right-turn-forward, + left-turn-forward, + } + + record camera-config { + horizontal: f32, + vertical: f32, + pan: f32, + tilt: f32, + zoom: f32, + roll: f32, + } + + variant camera-control { + movement(camera-movement), + config(camera-config), + } + + record generation-config { + negative-prompt: option, + seed: option, + scheduler: option, + guidance-scale: option, + aspect-ratio: option, + duration-seconds: option, + resolution: option, + model: option, + enable-audio: option, + enhance-prompt: option, + provider-options: option>, + lastframe: option, + static-mask: option, + dynamic-mask: option, + camera-control: option, + } + + enum aspect-ratio { + square, + portrait, + landscape, + cinema, + } + + enum resolution { + sd, + hd, + fhd, + uhd, + } + + record kv { + key: string, + value: string, + } + + record video { + uri: option, + base64-bytes: option>, + mime-type: string, + width: option, + height: option, + fps: option, + duration-seconds: option, + } + + variant job-status { + pending, + running, + succeeded, + failed(string), + } + + record video-result { + status: job-status, + videos: option>, + metadata: option>, + } + + record text-to-speech { + text: string, + voice-id: string, + language: voice-language, + speed: u32, + } + + variant audio-source { + from-text(text-to-speech), + from-audio(narration), + } + + record voice-info { + voice-id: string, + name: string, + language: voice-language, + preview-url: option, + } + + enum voice-language { + en, + zh + } + + enum single-image-effects { + bloombloom, + dizzydizzy, + fuzzyfuzzy, + squish, + expansion, + } + + enum dual-image-effects { + hug, + kiss, + heart-gesture, + } + + record dual-effect { + effect: dual-image-effects, + second-image: input-image, + } + + variant effect-type { + single(single-image-effects), + dual(dual-effect), + } +} + +interface video-generation { + use types.{media-input, generation-config, video-result, video-error}; + + generate: func(input: media-input, config: generation-config) -> result; + poll: func(job-id: string) -> result; + cancel: func(job-id: string) -> result; +} + +interface lip-sync { + use types.{base-video, audio-source, video-error, voice-info}; + + generate-lip-sync: func( + video: base-video, + audio: audio-source, + ) -> result; + + list-voices: func(language: option) -> result, video-error>; +} + +interface advanced { + use types.{video-error, kv, base-video, generation-config, input-image, effect-type}; + + extend-video: func( + video-id: string, + prompt: option, + negative-prompt: option, + cfg-scale: option, + provider-options: option>, + ) -> result; + + upscale-video: func( + input: base-video, + ) -> result; + + generate-video-effects: func( + input: input-image, + effect: effect-type, + model: option, + duration: option, + mode: option, + ) -> result; + + multi-image-generation: func( + input-images: list, + prompt: option, + config: generation-config, + ) -> result; +} + +world video-library { + import types; + import video-generation; + import lip-sync; + import advanced; + + export video-generation; + export lip-sync; + export advanced; + export types; +} diff --git a/test/video/.gitignore b/test/video/.gitignore new file mode 100644 index 000000000..ed09b5399 --- /dev/null +++ b/test/video/.gitignore @@ -0,0 +1,2 @@ +/golem-temp +/target diff --git a/test/video/.vscode/settings.json b/test/video/.vscode/settings.json new file mode 100644 index 000000000..7530c05d7 --- /dev/null +++ b/test/video/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.server.extraEnv": { "CARGO": "cargo-component" } +} \ No newline at end of file diff --git a/test/video/Cargo.lock b/test/video/Cargo.lock new file mode 100644 index 000000000..a2b508a71 --- /dev/null +++ b/test/video/Cargo.lock @@ -0,0 +1,1313 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "auditable-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7bf8143dfc3c0258df908843e169b5cc5fcf76c7718bd66135ef4a9cd558c5" +dependencies = [ + "semver", + "serde", + "serde_json", + "topological-sort", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "common-lib" +version = "0.1.0" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "git-version" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19" +dependencies = [ + "git-version-macro", +] + +[[package]] +name = "git-version-macro" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "golem-rust" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46aaf34adda9057718d79e808fb323b3247cb34ec9c38ff88e74824d703980dd" +dependencies = [ + "golem-rust-macro", + "golem-wasm-rpc", + "serde", + "serde_json", + "uuid", + "wit-bindgen", +] + +[[package]] +name = "golem-rust-macro" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab4174ebe45b8a1961eedeebc215bbc475aea4bdf4f2baa80cc6222fb0058da" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "golem-wasm-rpc" +version = "1.3.0-dev.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e80ad91fcaed123f283edb451862fb960f5f7a390c22a37186ca24770597d8ec" +dependencies = [ + "cargo_metadata", + "chrono", + "git-version", + "uuid", + "wit-bindgen-rt 0.40.0", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "git+https://github.com/zivergetech/reqwest?branch=update-may-2025#ff16d7ecf4325158baac5ab58e2199ba1eecd377" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "mime", + "percent-encoding", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tower-service", + "url", + "wit-bindgen-rt 0.41.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spdx" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" +dependencies = [ + "smallvec", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test_helper" +version = "0.0.0" +dependencies = [ + "golem-rust", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt 0.40.0", +] + +[[package]] +name = "test_video" +version = "0.0.1" +dependencies = [ + "golem-rust", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt 0.40.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom", + "js-sys", + "serde", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt 0.39.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d" +dependencies = [ + "anyhow", + "auditable-serde", + "flate2", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "url", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7091ed6c9abfa4e0a2ef3b39d0539da992d841fcf32c255f64fb98764ffee5" +dependencies = [ + "wit-bindgen-rt 0.40.0", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398c650cec1278cfb72e214ba26ef3440ab726e66401bd39c04f465ee3979e6b" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68faed92ae696b93ea9a7b67ba6c37bf09d72c6d9a70fa824a743c3020212f11" +dependencies = [ + "bitflags", + "futures", + "once_cell", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83903c8dcd8084a8a67ae08190122cf0e25dc37bdc239070a00f47e22d3f0aae" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7bf7f20495bcc7dc9f24c5fbcac9e919ca5ebdb7a1b1841d74447d3c8dd0c60" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/test/video/Cargo.toml b/test/video/Cargo.toml new file mode 100644 index 000000000..ae41bab4c --- /dev/null +++ b/test/video/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +resolver = "2" +members = ["components-rust/*", "common-rust/*"] + +[profile.release] +opt-level = "s" +lto = true + +[workspace.dependencies] +golem-rust = { version = "1.6.0", features = [ + "export_load_snapshot", + "export_save_snapshot", + "export_oplog_processor", +] } +reqwest = { git = "https://github.com/zivergetech/reqwest", branch = "update-may-2025", features = [ + "json", +] } +serde = { version = "1.0.0", features = ["derive"] } +serde_json = "1.0" +wit-bindgen-rt = { version = "0.40.0", features = ["bitflags"] } diff --git a/test/video/common-rust/common-lib/Cargo.toml b/test/video/common-rust/common-lib/Cargo.toml new file mode 100644 index 000000000..876f1d66d --- /dev/null +++ b/test/video/common-rust/common-lib/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "common-lib" +version = "0.1.0" +edition = "2021" diff --git a/test/video/common-rust/common-lib/src/lib.rs b/test/video/common-rust/common-lib/src/lib.rs new file mode 100644 index 000000000..15c6dcba7 --- /dev/null +++ b/test/video/common-rust/common-lib/src/lib.rs @@ -0,0 +1,3 @@ +pub fn example_common_function() -> &'static str { + "hello common" +} diff --git a/test/video/common-rust/golem.yaml b/test/video/common-rust/golem.yaml new file mode 100644 index 000000000..5d65c9ff5 --- /dev/null +++ b/test/video/common-rust/golem.yaml @@ -0,0 +1,47 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference +# For creating APIs see https://learn.golem.cloud/invoke/making-custom-apis + +templates: + rust: + profiles: + debug: + build: + - command: cargo component build + sources: + - src + - wit-generated + - ../../common-rust + - Cargo.toml + targets: + - ../../target/wasm32-wasip1/debug/{{ component_name | to_snake_case }}.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/{{ component_name | to_snake_case }}.wasm + linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}_debug.wasm + clean: + - src/bindings.rs + release: + build: + - command: cargo component build --release + sources: + - src + - wit-generated + - ../../common-rust + - Cargo.toml + targets: + - ../../target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/{{ component_name | to_snake_case }}.wasm + linkedWasm: ../../golem-temp/components/{{ component_name | to_snake_case }}_release.wasm + clean: + - src/bindings.rs + defaultProfile: debug +customCommands: + cargo-clean: + - command: cargo clean diff --git a/test/video/components-rust/.gitignore b/test/video/components-rust/.gitignore new file mode 100644 index 000000000..19195863f --- /dev/null +++ b/test/video/components-rust/.gitignore @@ -0,0 +1,3 @@ +/*/src/bindings.rs +/*/wit-generated +/*/golem-temp \ No newline at end of file diff --git a/test/video/components-rust/test-helper/Cargo.lock b/test/video/components-rust/test-helper/Cargo.lock new file mode 100644 index 000000000..bd98b1898 --- /dev/null +++ b/test/video/components-rust/test-helper/Cargo.lock @@ -0,0 +1,1376 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "test-helper" +version = "0.0.0" +dependencies = [ + "golem-rust", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "golem-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c967eb388fb81f9b9f4df5d5b6634de803f21cd410c1bf687202794a4fbc0267" +dependencies = [ + "golem-rust-macro", + "serde", + "serde_json", + "uuid", + "wit-bindgen-rt", +] + +[[package]] +name = "golem-rust-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb87f831cfe4371427c63f5f4cabcc3bae1b66974c8fbcf22be9274fee3a7d1" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[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 = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "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", +] + +[[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 = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "git+https://github.com/zivergetech/reqwest?branch=update-jun-2024#1cf59c67b93aa6292961f8948b93df5bca2753b6" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", + "wit-bindgen-rt", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[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-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/test/video/components-rust/test-helper/Cargo.toml b/test/video/components-rust/test-helper/Cargo.toml new file mode 100644 index 000000000..121fb6a40 --- /dev/null +++ b/test/video/components-rust/test-helper/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "test_helper" +version = "0.0.0" +edition = "2021" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] +required-features = [] + +[dependencies] +# To use common shared libs, use the following: +# common-lib = { path = "../../common-rust/common-lib" } + +golem-rust = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +wit-bindgen-rt = { workspace = true } + +[package.metadata.component.target] +path = "wit-generated" + +[package.metadata.component.bindings.with] + +[package.metadata.component.target.dependencies] +"test:helper-exports" = { path = "wit-generated/deps/test_helper-exports" } + +[package.metadata.component.bindings] +# See https://github.com/bytecodealliance/cargo-component/blob/main/src/metadata.rs#L62 + +# derives = ["serde::Serialize", "serde::Deserialize"] +# generate_unused_types = true \ No newline at end of file diff --git a/test/video/components-rust/test-helper/golem.yaml b/test/video/components-rust/test-helper/golem.yaml new file mode 100644 index 000000000..4c1a11ae1 --- /dev/null +++ b/test/video/components-rust/test-helper/golem.yaml @@ -0,0 +1,18 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.1.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.1.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference + +components: + test:helper: + template: rust + +# Example for adding dependencies for Worker to Worker communication: +# See https://learn.golem.cloud/docs/app-manifest#fields_dependencies for more information +# +#dependencies: +# test:helper: +# - target: +# type: wasm-rpc diff --git a/test/video/components-rust/test-helper/src/lib.rs b/test/video/components-rust/test-helper/src/lib.rs new file mode 100644 index 000000000..52ed26b30 --- /dev/null +++ b/test/video/components-rust/test-helper/src/lib.rs @@ -0,0 +1,38 @@ +#[allow(static_mut_refs)] +mod bindings; + +use crate::bindings::exports::test::helper_exports::test_helper_api::*; +// Import for using common lib (also see Cargo.toml for adding the dependency): +// use common_lib::example_common_function; +use std::cell::RefCell; + +/// This is one of any number of data types that our application +/// uses. Golem will take care to persist all application state, +/// whether that state is local to a function being executed or +/// global across the entire program. +struct State { + total: u64, +} + +thread_local! { + /// This holds the state of our application. + static STATE: RefCell = RefCell::new(State { + total: 0, + }); +} + +struct Component; + +impl Guest for Component { + fn inc_and_get() -> u64 { + // Call code from shared lib + // println!("{}", example_common_function()); + + STATE.with_borrow_mut(|state| { + state.total += 1; + state.total + }) + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/test/video/components-rust/test-helper/wit/test-helper.wit b/test/video/components-rust/test-helper/wit/test-helper.wit new file mode 100644 index 000000000..fec4ac2d2 --- /dev/null +++ b/test/video/components-rust/test-helper/wit/test-helper.wit @@ -0,0 +1,9 @@ +package test:helper; + +interface test-helper-api { + inc-and-get: func() -> u64; +} + +world test-helper { + export test-helper-api; +} diff --git a/test/video/components-rust/test-video/Cargo.lock b/test/video/components-rust/test-video/Cargo.lock new file mode 100644 index 000000000..e7f00cfeb --- /dev/null +++ b/test/video/components-rust/test-video/Cargo.lock @@ -0,0 +1,1376 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "test-video" +version = "0.0.1" +dependencies = [ + "golem-rust", + "reqwest", + "serde", + "serde_json", + "wit-bindgen-rt", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "golem-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c967eb388fb81f9b9f4df5d5b6634de803f21cd410c1bf687202794a4fbc0267" +dependencies = [ + "golem-rust-macro", + "serde", + "serde_json", + "uuid", + "wit-bindgen-rt", +] + +[[package]] +name = "golem-rust-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb87f831cfe4371427c63f5f4cabcc3bae1b66974c8fbcf22be9274fee3a7d1" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[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 = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "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", +] + +[[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 = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "git+https://github.com/zivergetech/reqwest?branch=update-jun-2024#1cf59c67b93aa6292961f8948b93df5bca2753b6" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", + "wit-bindgen-rt", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[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-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/test/video/components-rust/test-video/Cargo.toml b/test/video/components-rust/test-video/Cargo.toml new file mode 100644 index 000000000..27d42640d --- /dev/null +++ b/test/video/components-rust/test-video/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "test_video" +version = "0.0.1" +edition = "2021" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] +required-features = [] + +[features] +default = ["stability"] +stability = [] +runway = [] +kling = [] +veo = [] + +[dependencies] +# To use common shared libs, use the following: +# common-lib = { path = "../../common-rust/common-lib" } + +golem-rust = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +wit-bindgen-rt = { workspace = true } + +[package.metadata.component.target] +path = "wit-generated" + +[package.metadata.component.bindings.with] +"wasi:io/poll@0.2.0" = "golem_rust::wasm_rpc::wasi::io::poll" +"wasi:clocks/wall-clock@0.2.0" = "golem_rust::wasm_rpc::wasi::clocks::wall_clock" +"golem:rpc/types@0.2.0" = "golem_rust::wasm_rpc::golem_rpc_0_2_x::types" + +[package.metadata.component.target.dependencies] +"wasi:io" = { path = "wit-generated/deps/io" } +"wasi:clocks" = { path = "wit-generated/deps/clocks" } +"golem:rpc" = { path = "wit-generated/deps/golem-rpc" } +"golem:video" = { path = "wit-generated/deps/golem-video" } +"test:helper-client" = { path = "wit-generated/deps/test_helper-client" } +"test:video-exports" = { path = "wit-generated/deps/test_video-exports" } + +[package.metadata.component.bindings] +# See https://github.com/bytecodealliance/cargo-component/blob/main/src/metadata.rs#L62 + +# derives = ["serde::Serialize", "serde::Deserialize"] +# generate_unused_types = true \ No newline at end of file diff --git a/test/video/components-rust/test-video/golem.yaml b/test/video/components-rust/test-video/golem.yaml new file mode 100644 index 000000000..d63b758a1 --- /dev/null +++ b/test/video/components-rust/test-video/golem.yaml @@ -0,0 +1,220 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference +# For creating APIs see https://learn.golem.cloud/invoke/making-custom-apis + +components: + test:video: + profiles: + # DEBUG PROFILES + stability-debug: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --no-default-features --features stability + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_video_stability.wasm ../../target/wasm32-wasip1/debug/test_video.wasm -o ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - ../../../../target/wasm32-wasip1/debug/golem_video_stability.wasm + targets: + - ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_stability.wasm + clean: + - src/bindings.rs + runway-debug: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --no-default-features --features runway + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_video_runway.wasm ../../target/wasm32-wasip1/debug/test_video.wasm -o ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - ../../../../target/wasm32-wasip1/debug/golem_video_runway.wasm + targets: + - ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_runway_debug.wasm + clean: + - src/bindings.rs + kling-debug: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --no-default-features --features kling + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_video_kling.wasm ../../target/wasm32-wasip1/debug/test_video.wasm -o ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - ../../../../target/wasm32-wasip1/debug/golem_video_kling.wasm + targets: + - ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_kling_debug.wasm + clean: + - src/bindings.rs + veo-debug: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --no-default-features --features veo + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/debug/golem_video_veo.wasm ../../target/wasm32-wasip1/debug/test_video.wasm -o ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/debug/test_video.wasm + - ../../../../target/wasm32-wasip1/debug/golem_video_veo.wasm + targets: + - ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/debug/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_veo_debug.wasm + clean: + - src/bindings.rs + # RELEASE PROFILES + stability-release: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --release --no-default-features --features stability + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/release/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_video_stability.wasm ../../target/wasm32-wasip1/release/test_video.wasm -o ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/release/test_video.wasm + - ../../../../target/wasm32-wasip1/release/golem_video_stability.wasm + targets: + - ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_stability_release.wasm + clean: + - src/bindings.rs + runway-release: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --release --no-default-features --features runway + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/release/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_video_runway.wasm ../../target/wasm32-wasip1/release/test_video.wasm -o ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/release/test_video.wasm + - ../../../../target/wasm32-wasip1/release/golem_video_runway.wasm + targets: + - ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_runway_release.wasm + clean: + - src/bindings.rs + kling-release: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --release --no-default-features --features kling + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/release/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_video_kling.wasm ../../target/wasm32-wasip1/release/test_video.wasm -o ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/release/test_video.wasm + - ../../../../target/wasm32-wasip1/release/golem_video_kling.wasm + targets: + - ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_kling_release.wasm + clean: + - src/bindings.rs + veo-release: + files: + - sourcePath: ../../data/old.png + targetPath: /data/old.png + permissions: read-only + build: + - command: cargo component build --release --no-default-features --features veo + sources: + - src + - wit-generated + - ../../common-rust + targets: + - ../../target/wasm32-wasip1/release/test_video.wasm + - command: wac plug --plug ../../../../target/wasm32-wasip1/release/golem_video_veo.wasm ../../target/wasm32-wasip1/release/test_video.wasm -o ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sources: + - ../../target/wasm32-wasip1/release/test_video.wasm + - ../../../../target/wasm32-wasip1/release/golem_video_veo.wasm + targets: + - ../../target/wasm32-wasip1/release/test_video_plugged.wasm + sourceWit: wit + generatedWit: wit-generated + componentWasm: ../../target/wasm32-wasip1/release/test_video_plugged.wasm + linkedWasm: ../../golem-temp/components/test_video_veo_release.wasm + clean: + - src/bindings.rs + defaultProfile: stability-debug + + +dependencies: + test:video: + - target: test:helper + type: wasm-rpc diff --git a/test/video/components-rust/test-video/src/lib.rs b/test/video/components-rust/test-video/src/lib.rs new file mode 100644 index 000000000..d31404df6 --- /dev/null +++ b/test/video/components-rust/test-video/src/lib.rs @@ -0,0 +1,363 @@ +#[allow(static_mut_refs)] +mod bindings; + +use golem_rust::atomically; +use crate::bindings::exports::test::video_exports::test_video_api::*; +use crate::bindings::golem::video::types; +use crate::bindings::golem::video::video_generation; +use crate::bindings::test::helper_client::test_helper_client::TestHelperApi; +use std::fs; +use std::fs::File; +use std::io::Read; +use std::thread; +use std::time::Duration; + +struct Component; + +impl Guest for Component { + /// test1 demonstrates text-to-video generation with a simple prompt + fn test1() -> String { + println!("Test1: Text to video generation"); + + // Create video generation configuration + let config = types::GenerationConfig { + negative_prompt: None, + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: None, + model: None, + duration_seconds: None, + resolution: None, + enable_audio: Some(false), + enhance_prompt: Some(false), + provider_options: None, + lastframe: None, + static_mask: None, + dynamic_mask: None, + camera_control: None, + }; + + // Create text prompt for video generation + let media_input = types::MediaInput::Text("A beautiful sunset over the ocean, orange and red hues".to_string()); + + println!("Sending text-to-video generation request..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test1") + } + + /// test2 demonstrates image-to-video generation with durability testing + fn test2() -> String { + println!("Test2: Image to video with durability test"); + + // Load test image as inline raw bytes + let (image_bytes, image_mime_type) = match load_file_bytes("/data/old.png") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(err) => return format!("ERROR: Failed to open old.png: {}", err), + }; + + // Create video generation configuration + let config = types::GenerationConfig { + negative_prompt: None, + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: None, + model: None, + duration_seconds: None, + resolution: None, + enable_audio: Some(false), + enhance_prompt: Some(false), + provider_options: None, + lastframe: None, + static_mask: None, + dynamic_mask: None, + camera_control: None, + }; + + // Create media input with image data and 'none' role + let media_input = types::MediaInput::Image(types::Reference { + data: types::InputImage { + data: types::MediaData::Bytes(types::RawBytes { + bytes: image_bytes, + mime_type: image_mime_type, + }), + }, + prompt: Some("A simple motion effect".to_string()), + role: None, // Role set to 'none' as specified + }); + + println!("Sending image-to-video generation request..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + poll_job_until_complete_with_durability(&job_id, "test2") + } + + fn test3() -> String { + println!("Test3: Image to video with 'last' role and URL"); + + // Create video generation configuration + let config = types::GenerationConfig { + negative_prompt: Some("blurry, distorted".to_string()), + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: None, + model: None, + duration_seconds: None, + resolution: None, + enable_audio: Some(false), + enhance_prompt: Some(false), + provider_options: None, + lastframe: None, + static_mask: None, + dynamic_mask: None, + camera_control: None, + }; + + // Create media input with image URL and 'last' role + let media_input = types::MediaInput::Image(types::Reference { + data: types::InputImage { + data: types::MediaData::Url("https://wallpapercave.com/wp/wp12088891.jpg".to_string()), + }, + prompt: Some("A serene landscape transforming with gentle motion".to_string()), + role: Some(types::ImageRole::Last), // Set to 'last' as specified + }); + + println!("Sending image-to-video generation request with 'last' role..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test3") + } + + fn test4() -> String { + println!("Test4: Video to video generation (VEO only)"); + + // Load the output from test1 as input video (inline raw bytes) + let (video_bytes, video_mime_type) = match load_file_bytes("/output/video-test1-0.mp4") { + Ok((bytes, mime_type)) => (bytes, mime_type), + Err(_) => { + // Fallback message if test1 output not available + return "Test4: VEO video-to-video transformation (requires test1 output)".to_string(); + } + }; + + let config = types::GenerationConfig { + negative_prompt: Some("artifacts, glitches".to_string()), + seed: None, + scheduler: None, + guidance_scale: None, + aspect_ratio: None, + model: None, + duration_seconds: None, + resolution: None, + enable_audio: Some(false), + enhance_prompt: Some(true), + provider_options: None, + lastframe: None, + static_mask: None, + dynamic_mask: None, + camera_control: None, + }; + + // Create media input with video data (inline raw bytes) + let media_input = types::MediaInput::Video(types::BaseVideo { + data: types::MediaData::Bytes(types::RawBytes { + bytes: video_bytes, + mime_type: video_mime_type, + }), + }); + + println!("Sending video-to-video generation request..."); + let job_id = match video_generation::generate(&media_input, &config) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to generate video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test4") + } + + fn test5() -> String { + use crate::bindings::golem::video::advanced; + + println!("Test5: Video upscale (Runway only)"); + + let base_video = types::BaseVideo { + data: types::MediaData::Url("https://v1-kling.kechuangai.com/kcdn/cdn-kcdn112452/kling-api-document/videos/a-girl-on-unicorn.mp4".to_string()), + }; + + println!("Sending video upscale request..."); + let job_id = match advanced::upscale_video(&base_video) { + Ok(id) => id.trim().to_string(), + Err(error) => return format!("ERROR: Failed to upscale video: {:?}", error), + }; + + poll_job_until_complete(&job_id, "test5") + } + +} + +fn save_video_result(video_result: &types::VideoResult, test_name: &str) -> String { + if let Some(videos) = &video_result.videos { + for (i, video_data) in videos.iter().enumerate() { + let filename = format!("/output/video-{}-{}.mp4", test_name, i); + + // Create output directory if it doesn't exist + if let Err(err) = fs::create_dir_all("/output") { + return format!("Failed to create output directory: {}", err); + } + + // Save the video data + match &video_data.base64_bytes { + Some(video_bytes) => { + match fs::write(&filename, video_bytes) { + Ok(_) => { + return filename; + } + Err(err) => { + return format!("Failed to save video to {}: {}", filename, err); + } + } + } + None => { + if let Some(uri) = &video_data.uri { + return format!("Video available at URI: {}", uri); + } else { + return "No video data or URI available".to_string(); + } + } + } + } + "No videos in result".to_string() + } else { + "No videos in result".to_string() + } +} + +fn load_file_bytes(path: &str) -> Result<(Vec, String), String> { + println!("Reading file from: {}", path); + let mut file = match File::open(path) { + Ok(file) => file, + Err(err) => return Err(format!("Failed to open {}: {}", path, err)), + }; + + let mut buffer = Vec::new(); + match file.read_to_end(&mut buffer) { + Ok(_) => { + println!("Successfully read {} bytes from {}", buffer.len(), path); + let mime_type = match path.rsplit('.').next() { + Some("png") => "image/png".to_string(), + Some("mp4") => "video/mp4".to_string(), + Some("mp3") => "audio/mpeg".to_string(), + _ => "application/octet-stream".to_string(), // Default or unknown + }; + Ok((buffer, mime_type)) + } + Err(err) => Err(format!("Failed to read {}: {}", path, err)), + } +} + +fn poll_job_until_complete(job_id: &str, test_name: &str) -> String { + println!("Polling for {} results with job ID: {}", test_name, job_id); + + // Wait 5 seconds after job creation before starting polling + println!("Waiting 5 seconds for job initialization..."); + thread::sleep(Duration::from_secs(5)); + + // Poll every 9 seconds until completion + loop { + match video_generation::poll(&job_id) { + Ok(video_result) => { + match video_result.status { + types::JobStatus::Pending => { + println!("{} is pending...", test_name); + } + types::JobStatus::Running => { + println!("{} is running...", test_name); + } + types::JobStatus::Succeeded => { + println!("{} completed successfully!", test_name); + let file_path = save_video_result(&video_result, test_name); + return format!("{} generated successfully. Saved to: {}", test_name, file_path); + } + types::JobStatus::Failed(error_msg) => { + return format!("{} failed: {}", test_name, error_msg); + } + } + } + Err(error) => { + return format!("Error polling {}: {:?}", test_name, error); + } + } + + // Wait 9 seconds before polling again + thread::sleep(Duration::from_secs(9)); + } +} + +fn poll_job_until_complete_with_durability(job_id: &str, test_name: &str) -> String { + println!("Polling for {} results with job ID: {} (with durability test)", test_name, job_id); + + // Wait 5 seconds after job creation before starting polling + println!("Waiting 5 seconds for job initialization..."); + thread::sleep(Duration::from_secs(5)); + + let name = std::env::var("GOLEM_WORKER_NAME").unwrap(); + let mut round = 0; + + // Poll every 5 seconds until completion + loop { + match video_generation::poll(&job_id) { + Ok(video_result) => { + match video_result.status { + types::JobStatus::Pending => { + println!("{} is pending... (round {})", test_name, round); + } + types::JobStatus::Running => { + println!("{} is running... (round {})", test_name, round); + } + types::JobStatus::Succeeded => { + println!("{} completed successfully after {} rounds!", test_name, round); + let file_path = save_video_result(&video_result, test_name); + return format!("{} generated successfully. Saved to: {} (durability test passed)", test_name, file_path); + } + types::JobStatus::Failed(error_msg) => { + return format!("{} failed: {}", test_name, error_msg); + } + } + } + Err(error) => { + return format!("Error polling {}: {:?}", test_name, error); + } + } + + // Durability test simulation: simulate a crash during polling, but only first time + // After automatic recovery it will continue and finish the request successfully + if round == 1 { + atomically(|| { + let client = TestHelperApi::new(&name); + let answer = client.blocking_inc_and_get(); + if answer == 1 { + panic!("Simulating crash during durability test") + } + }); + } + + round += 1; + + // Wait 9 seconds before polling again + thread::sleep(Duration::from_secs(9)); + } +} + +bindings::export!(Component with_types_in bindings); \ No newline at end of file diff --git a/test/video/components-rust/test-video/wit/test-video.wit b/test/video/components-rust/test-video/wit/test-video.wit new file mode 100644 index 000000000..58146f4b4 --- /dev/null +++ b/test/video/components-rust/test-video/wit/test-video.wit @@ -0,0 +1,19 @@ +package test:video; + +// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax + +interface test-video-api { + test1: func() -> string; + test2: func() -> string; + test3: func() -> string; + test4: func() -> string; + test5: func() -> string; +} + +world test-video { + import golem:video/types@1.0.0; + import golem:video/video-generation@1.0.0; + import golem:video/lip-sync@1.0.0; + import golem:video/advanced@1.0.0; + export test-video-api; +} diff --git a/test/video/data/old.png b/test/video/data/old.png new file mode 100644 index 000000000..076034120 Binary files /dev/null and b/test/video/data/old.png differ diff --git a/test/video/golem.yaml b/test/video/golem.yaml new file mode 100644 index 000000000..9b87334db --- /dev/null +++ b/test/video/golem.yaml @@ -0,0 +1,13 @@ +# Schema for IDEA: +# $schema: https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json +# Schema for vscode-yaml +# yaml-language-server: $schema=https://schema.golem.cloud/app/golem/1.2.2.1/golem.schema.json + +# See https://learn.golem.cloud/docs/app-manifest#field-reference for field reference +# For creating APIs see https://learn.golem.cloud/invoke/making-custom-apis + +includes: +- common-*/golem.yaml +- components-*/*/golem.yaml +witDeps: +- wit/deps diff --git a/test/video/wit/deps/golem-video/golem-video.wit b/test/video/wit/deps/golem-video/golem-video.wit new file mode 100644 index 000000000..8a2ecf138 --- /dev/null +++ b/test/video/wit/deps/golem-video/golem-video.wit @@ -0,0 +1,256 @@ +package golem:video@1.0.0; + +interface types { + variant video-error { + invalid-input(string), + unsupported-feature(string), + quota-exceeded, + generation-failed(string), + cancelled, + internal-error(string), + } + + variant media-input { + text(string), + image(reference), + video(base-video), + } + + record reference { + data: input-image, + prompt: option, + role: option, + } + + enum image-role { + first, + last, + } + + record input-image { + data: media-data, + } + + record base-video { + data: media-data, + } + + record narration { + data: media-data, + } + + variant media-data { + url(string), + bytes(raw-bytes), + } + + record raw-bytes { + bytes: list, + mime-type: string, + } + + record static-mask { + mask: input-image, + } + + record dynamic-mask { + mask: input-image, + trajectories: list, + } + + record position { + x: u32, + y: u32, + } + + enum camera-movement { + simple, + down-back, + forward-up, + right-turn-forward, + left-turn-forward, + } + + record camera-config { + horizontal: f32, + vertical: f32, + pan: f32, + tilt: f32, + zoom: f32, + roll: f32, + } + + variant camera-control { + movement(camera-movement), + config(camera-config), + } + + record generation-config { + negative-prompt: option, + seed: option, + scheduler: option, + guidance-scale: option, + aspect-ratio: option, + duration-seconds: option, + resolution: option, + model: option, + enable-audio: option, + enhance-prompt: option, + provider-options: option>, + lastframe: option, + static-mask: option, + dynamic-mask: option, + camera-control: option, + } + + enum aspect-ratio { + square, + portrait, + landscape, + cinema, + } + + enum resolution { + sd, + hd, + fhd, + uhd, + } + + record kv { + key: string, + value: string, + } + + record video { + uri: option, + base64-bytes: option>, + mime-type: string, + width: option, + height: option, + fps: option, + duration-seconds: option, + } + + variant job-status { + pending, + running, + succeeded, + failed(string), + } + + record video-result { + status: job-status, + videos: option>, + metadata: option>, + } + + record text-to-speech { + text: string, + voice-id: string, + language: voice-language, + speed: u32, + } + + variant audio-source { + from-text(text-to-speech), + from-audio(narration), + } + + record voice-info { + voice-id: string, + name: string, + language: voice-language, + preview-url: option, + } + + enum voice-language { + en, + zh + } + + enum single-image-effects { + bloombloom, + dizzydizzy, + fuzzyfuzzy, + squish, + expansion, + } + + enum dual-image-effects { + hug, + kiss, + heart-gesture, + } + + record dual-effect { + effect: dual-image-effects, + second-image: input-image, + } + + variant effect-type { + single(single-image-effects), + dual(dual-effect), + } +} + +interface video-generation { + use types.{media-input, generation-config, video-result, video-error}; + + generate: func(input: media-input, config: generation-config) -> result; + poll: func(job-id: string) -> result; + cancel: func(job-id: string) -> result; +} + +interface lip-sync { + use types.{base-video, audio-source, video-error, voice-info}; + + generate-lip-sync: func( + video: base-video, + audio: audio-source, + ) -> result; + + list-voices: func(language: option) -> result, video-error>; +} + +interface advanced { + use types.{video-error, kv, base-video, generation-config, input-image, effect-type}; + + extend-video: func( + video-id: string, + prompt: option, + negative-prompt: option, + cfg-scale: option, + provider-options: option>, + ) -> result; + + upscale-video: func( + input: base-video, + ) -> result; + + generate-video-effects: func( + input: input-image, + effect: effect-type, + model: option, + duration: option, + mode: option, + ) -> result; + + multi-image-generation: func( + input-images: list, + prompt: option, + config: generation-config, + ) -> result; +} + +world video-library { + import types; + import video-generation; + import lip-sync; + import advanced; + + export video-generation; + export lip-sync; + export advanced; + export types; +} diff --git a/video/Makefile.toml b/video/Makefile.toml new file mode 100644 index 000000000..103ba9529 --- /dev/null +++ b/video/Makefile.toml @@ -0,0 +1,148 @@ + +[config] +default_to_workspace = false +skip_core_tasks = true + +[tasks.build] +run_task = { name = [ + "build-veo", + "build-stability", + "build-kling", + "build-runway", +] } + +[tasks.build-portable] +run_task = { name = [ + "build-veo-portable", + "build-stability-portable", + "build-kling-portable", + "build-runway-portable", +] } + +[tasks.release-build] +run_task = { name = [ + "release-build-veo", + "release-build-stability", + "release-build-kling", + "release-build-runway", +] } + +[tasks.release-build-portable] +run_task = { name = [ + "release-build-veo-portable", + "release-build-stability-portable", + "release-build-kling-portable", + "release-build-runway-portable", +] } + +[tasks.build-veo] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-veo"] + +[tasks.build-veo-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-veo", "--no-default-features"] + +[tasks.build-stability] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-stability"] + +[tasks.build-stability-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-stability", "--no-default-features"] + +[tasks.build-kling] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-kling"] + +[tasks.build-kling-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-kling", "--no-default-features"] + +[tasks.build-runway] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-runway"] + +[tasks.build-runway-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-runway", "--no-default-features"] + +[tasks.release-build-veo] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-veo", "--release"] + +[tasks.release-build-veo-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-veo", "--release", "--no-default-features"] + +[tasks.release-build-stability] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-stability", "--release"] + +[tasks.release-build-stability-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-stability", "--release", "--no-default-features"] + +[tasks.release-build-kling] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-kling", "--release"] + +[tasks.release-build-kling-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-kling", "--release", "--no-default-features"] + +[tasks.release-build-runway] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-runway", "--release"] + +[tasks.release-build-runway-portable] +install_crate = { crate_name = "cargo-component", version = "0.20.0" } +command = "cargo-component" +args = ["build", "-p", "golem-video-runway", "--release", "--no-default-features"] + +[tasks.wit-update] +install_crate = { crate_name = "wit-deps-cli" } +command = "wit-deps" +args = ["update"] + +[tasks.wit] +#dependencies = ["wit-update"] + +script_runner = "@duckscript" +script = """ +modules = array video veo stability kling runway + +for module in ${modules} + rm -r ${module}/wit/deps + mkdir ${module}/wit/deps/golem-video + cp wit/golem-video.wit ${module}/wit/deps/golem-video/golem-video.wit + + echo "Copied WIT for module video::${module}" +end + +# Copy WIT files for integration tests +rm -r ../test/video/wit +mkdir ../test/video/wit/deps/golem-video +cp wit/golem-video.wit ../test/video/wit/deps/golem-video/golem-video.wit + +rm -r ../test/video-advanced/wit +mkdir ../test/video-advanced/wit/deps/golem-video +cp wit/golem-video.wit ../test/video-advanced/wit/deps/golem-video/golem-video.wit + +echo "Copied WIT for module video" +""" diff --git a/video/kling/Cargo.toml b/video/kling/Cargo.toml new file mode 100644 index 000000000..12322b467 --- /dev/null +++ b/video/kling/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "golem-video-kling" +version = "0.0.0" +edition = "2021" +license = "Apache-2.0" +homepage = "https://golem.cloud" +repository = "https://github.com/golemcloud/golem-llm" +description = "WebAssembly component for working with Kuaishou Kling video APIs, with special support for Golem Cloud" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["durability"] +durability = ["golem-rust/durability", "golem-video/durability"] + +[dependencies] +golem-video = { path = "../video", version = "0.0.0", default-features = false } + +golem-rust = { workspace = true } +log = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +wit-bindgen-rt = { workspace = true } +base64 = { workspace = true } +hmac-sha256 = "1.1" + +[package.metadata.component] +package = "golem:video-kling" + +[package.metadata.component.bindings] +generate_unused_types = true + +[package.metadata.component.bindings.with] +"golem:video/types@1.0.0" = "golem_video::golem::video::types" +"golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +"golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +"golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" + +[package.metadata.component.target] +path = "wit" + +[package.metadata.component.target.dependencies] +"golem:video" = { path = "wit/deps/golem-video" } \ No newline at end of file diff --git a/video/kling/src/authentication.rs b/video/kling/src/authentication.rs new file mode 100644 index 000000000..40e5de471 --- /dev/null +++ b/video/kling/src/authentication.rs @@ -0,0 +1,62 @@ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; +use hmac_sha256::HMAC; +use log::{debug, trace}; +use serde_json::json; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Generate JWT token for Kling API authentication +pub fn generate_jwt_token(access_key: &str, secret_key: &str) -> Result { + // Get current time in seconds since Unix epoch + let now = get_current_time_seconds()?; + + trace!("Generating JWT token with timestamp: {now}"); + + // Create JWT header (HS256 algorithm) + let header = json!({ + "alg": "HS256", + "typ": "JWT" + }); + + // Create JWT payload/claims matching Python implementation exactly + let payload = json!({ + "iss": access_key, + "exp": now + 180, // Valid for 3 minutes + "nbf": now.saturating_sub(5) // Effective 5 seconds ago + }); + + // Encode header and payload to base64url + let header_b64 = base64url_encode( + &serde_json::to_vec(&header).map_err(|e| format!("Failed to serialize header: {e}"))?, + ); + let payload_b64 = base64url_encode( + &serde_json::to_vec(&payload).map_err(|e| format!("Failed to serialize payload: {e}"))?, + ); + + // Create the message to sign + let message = format!("{header_b64}.{payload_b64}"); + + // Create HMAC-SHA256 signature + let signature = HMAC::mac(message.as_bytes(), secret_key.as_bytes()); + let signature_b64 = base64url_encode(&signature); + + // Combine all parts to create the final JWT + let token = format!("{message}.{signature_b64}"); + + // Print the generated token for debugging + debug!("Generated JWT token: {token}"); + + Ok(token) +} + +/// Get current time in seconds since Unix epoch +fn get_current_time_seconds() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_secs()) + .map_err(|e| format!("Failed to get current time: {e}")) +} + +/// Encode bytes to base64url (no padding) +fn base64url_encode(data: &[u8]) -> String { + URL_SAFE_NO_PAD.encode(data) +} diff --git a/video/kling/src/bindings.rs b/video/kling/src/bindings.rs new file mode 100644 index 000000000..2b7f3ad22 --- /dev/null +++ b/video/kling/src/bindings.rs @@ -0,0 +1,148 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +// * runtime_path: "wit_bindgen_rt" +// * with "golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" +// * with "golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +// * with "golem:video/types@1.0.0" = "golem_video::golem::video::types" +// * with "golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +// * generate_unused_types +use golem_video::golem::video::types as __with_name0; +use golem_video::golem::video::video_generation as __with_name1; +use golem_video::golem::video::lip_sync as __with_name2; +use golem_video::golem::video::advanced as __with_name3; +use golem_video::golem::video::types as __with_name4; +use golem_video::golem::video::video_generation as __with_name5; +use golem_video::golem::video::lip_sync as __with_name6; +use golem_video::golem::video::advanced as __with_name7; +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:video-kling@1.0.0:video-library:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 5481] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe5)\x01A\x02\x01A\x1a\ +\x01BO\x01q\x06\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-e\ +xceeded\0\0\x11generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\ +\x04\0\x0bvideo-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\ +\x02\x01p}\x01r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\ +\x02\x03url\x01s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04\ +data\x08\x04\0\x0binput-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06p\ +rompt\x0b\x04role\x0c\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0a\ +base-video\x03\0\x0f\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\ +\0\x04\0\x0bmedia-input\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\ +\x01r\x01\x04mask\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08\ +position\x03\0\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdy\ +namic-mask\x03\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn\ +-forward\x11left-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahor\ +izontalv\x08verticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\ +\0\x1e\x01q\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-contro\ +l\x03\0\x20\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspec\ +t-ratio\x03\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01\ +r\x02\x03keys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p\ +'\x01k-\x01k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed\ +(\x09scheduler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0ar\ +esolution+\x05model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\ +\x09lastframe/\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11gene\ +ration-config\x03\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime\ +-types\x05width6\x06height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01\ +q\x04\x07pending\0\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0aj\ +ob-status\x03\09\x01p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0c\ +video-result\x03\0=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\ +\x04texts\x08voice-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0\ +A\x01q\x02\x09from-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-sourc\ +e\x03\0C\x01r\x04\x08voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\ +\0\x0avoice-info\x03\0E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06sq\ +uish\x09expansion\x04\0\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0d\ +heart-gesture\x04\0\x12dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csec\ +ond-image\x0a\x04\0\x0bdual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\ +\x01\xcc\0\0\x04\0\x0beffect-type\x03\0M\x03\0\x17golem:video/types@1.0.0\x05\0\x02\ +\x03\0\0\x0bmedia-input\x02\x03\0\0\x11generation-config\x02\x03\0\0\x0cvideo-re\ +sult\x02\x03\0\0\x0bvideo-error\x01B\x10\x02\x03\x02\x01\x01\x04\0\x0bmedia-inpu\ +t\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\x03\0\x02\x02\x03\x02\x01\ +\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\ +\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06config\x03\0\x08\x04\0\x08gene\ +rate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\x0a\x04\0\x04poll\x01\x0b\ +\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x03\0\"golem:video/video-gene\ +ration@1.0.0\x05\x05\x02\x03\0\0\x0abase-video\x02\x03\0\0\x0caudio-source\x02\x03\ +\0\0\x0avoice-info\x01B\x10\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\0\x02\x03\ +\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\x02\x01\x04\x04\0\x0bvideo-\ +error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-info\x03\0\x06\x01j\x01s\x01\x05\ +\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11generate-lip-sync\x01\x09\x01\ +ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08language\x0a\0\x0c\x04\0\x0blist-vo\ +ices\x01\x0d\x03\0\x1agolem:video/lip-sync@1.0.0\x05\x09\x02\x03\0\0\x02kv\x02\x03\ +\0\0\x0binput-image\x02\x03\0\0\x0beffect-type\x01B\x1a\x02\x03\x02\x01\x04\x04\0\ +\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\ +\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\x02\x01\x02\x04\0\x11generation-confi\ +g\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0binput-image\x03\0\x08\x02\x03\x02\x01\x0c\ +\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01\ +@\x05\x08video-ids\x06prompt\x0c\x0fnegative-prompt\x0c\x09cfg-scale\x0d\x10prov\ +ider-options\x0f\0\x10\x04\0\x0cextend-video\x01\x11\x01@\x01\x05input\x05\0\x10\ +\x04\0\x0dupscale-video\x01\x12\x01@\x05\x05input\x09\x06effect\x0b\x05model\x0c\ +\x08duration\x0d\x04mode\x0c\0\x10\x04\0\x16generate-video-effects\x01\x13\x01p\x09\ +\x01@\x03\x0cinput-images\x14\x06prompt\x0c\x06config\x07\0\x10\x04\0\x16multi-i\ +mage-generation\x01\x15\x03\0\x1agolem:video/advanced@1.0.0\x05\x0d\x01BO\x01q\x06\ +\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-exceeded\0\0\x11\ +generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\x04\0\x0bvide\ +o-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\x02\x01p}\x01\ +r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\x02\x03url\x01\ +s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04data\x08\x04\0\x0b\ +input-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06prompt\x0b\x04role\x0c\ +\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0abase-video\x03\0\x0f\ +\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\0\x04\0\x0bmedia-in\ +put\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\x01r\x01\x04mask\ +\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08position\x03\0\ +\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdynamic-mask\x03\ +\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn-forward\x11le\ +ft-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahorizontalv\x08ve\ +rticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\0\x1e\x01q\ +\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-control\x03\0\x20\ +\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspect-ratio\x03\ +\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01r\x02\x03k\ +eys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p'\x01k-\x01\ +k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed(\x09schedu\ +ler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0aresolution+\x05\ +model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\x09lastframe/\ +\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11generation-config\x03\ +\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime-types\x05width6\x06\ +height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01q\x04\x07pending\0\ +\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0ajob-status\x03\09\x01\ +p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0cvideo-result\x03\0\ +=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\x04texts\x08voice\ +-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0A\x01q\x02\x09from\ +-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-source\x03\0C\x01r\x04\x08\ +voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\0\x0avoice-info\x03\0\ +E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06squish\x09expansion\x04\0\ +\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0dheart-gesture\x04\0\x12\ +dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csecond-image\x0a\x04\0\x0b\ +dual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\x01\xcc\0\0\x04\0\x0be\ +ffect-type\x03\0M\x04\0\x17golem:video/types@1.0.0\x05\x0e\x01B\x10\x02\x03\x02\x01\ +\x01\x04\0\x0bmedia-input\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\ +\x03\0\x02\x02\x03\x02\x01\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\ +\x04\0\x0bvideo-error\x03\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06confi\ +g\x03\0\x08\x04\0\x08generate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\ +\x0a\x04\0\x04poll\x01\x0b\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x04\ +\0\"golem:video/video-generation@1.0.0\x05\x0f\x01B\x10\x02\x03\x02\x01\x06\x04\0\ +\x0abase-video\x03\0\0\x02\x03\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\ +\x02\x01\x04\x04\0\x0bvideo-error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-i\ +nfo\x03\0\x06\x01j\x01s\x01\x05\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11\ +generate-lip-sync\x01\x09\x01ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08langua\ +ge\x0a\0\x0c\x04\0\x0blist-voices\x01\x0d\x04\0\x1agolem:video/lip-sync@1.0.0\x05\ +\x10\x01B\x1a\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\ +\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\ +\x02\x01\x02\x04\0\x11generation-config\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0bi\ +nput-image\x03\0\x08\x02\x03\x02\x01\x0c\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01\ +kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01@\x05\x08video-ids\x06prompt\x0c\x0fne\ +gative-prompt\x0c\x09cfg-scale\x0d\x10provider-options\x0f\0\x10\x04\0\x0cextend\ +-video\x01\x11\x01@\x01\x05input\x05\0\x10\x04\0\x0dupscale-video\x01\x12\x01@\x05\ +\x05input\x09\x06effect\x0b\x05model\x0c\x08duration\x0d\x04mode\x0c\0\x10\x04\0\ +\x16generate-video-effects\x01\x13\x01p\x09\x01@\x03\x0cinput-images\x14\x06prom\ +pt\x0c\x06config\x07\0\x10\x04\0\x16multi-image-generation\x01\x15\x04\0\x1agole\ +m:video/advanced@1.0.0\x05\x11\x04\0%golem:video-kling/video-library@1.0.0\x04\0\ +\x0b\x13\x01\0\x0dvideo-library\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0d\ +wit-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/video/kling/src/client.rs b/video/kling/src/client.rs new file mode 100644 index 000000000..d54b79809 --- /dev/null +++ b/video/kling/src/client.rs @@ -0,0 +1,528 @@ +use crate::authentication::generate_jwt_token; +use golem_video::error::{from_reqwest_error, video_error_from_status}; +use golem_video::exports::golem::video::types::VideoError; +use log::trace; +use reqwest::{Client, Method, Response}; +use serde::{Deserialize, Serialize}; + +// For older users, the api endpoint is https://api.klingai.com +const BASE_URL: &str = "https://api-singapore.klingai.com"; + +/// The Kling API client for video generation +pub struct KlingApi { + access_key: String, + secret_key: String, + client: Client, +} + +impl KlingApi { + pub fn new(access_key: String, secret_key: String) -> Self { + let client = Client::builder() + .default_headers(reqwest::header::HeaderMap::new()) + .build() + .expect("Failed to initialize HTTP client"); + Self { + access_key, + secret_key, + client, + } + } + + fn get_auth_header(&self) -> Result { + let token = generate_jwt_token(&self.access_key, &self.secret_key) + .map_err(|e| VideoError::InternalError(format!("JWT token generation failed: {e}")))?; + Ok(format!("Bearer {token}")) + } + + pub fn generate_text_to_video( + &self, + request: TextToVideoRequest, + ) -> Result { + trace!("Sending text-to-video request to Kling API"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/videos/text2video")) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + pub fn generate_image_to_video( + &self, + request: ImageToVideoRequest, + ) -> Result { + trace!("Sending image-to-video request to Kling API"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/videos/image2video")) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + pub fn generate_multi_image_to_video( + &self, + request: MultiImageToVideoRequest, + ) -> Result { + trace!("Sending multi-image-to-video request to Kling API"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request( + Method::POST, + format!("{BASE_URL}/v1/videos/multi-image2video"), + ) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + pub fn poll_generation(&self, task_id: &str) -> Result { + trace!("Polling generation status for ID: {task_id}"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request( + Method::GET, + format!("{BASE_URL}/v1/videos/text2video/{task_id}"), + ) + .header("Authorization", auth_header) + .send() + .map_err(|err| from_reqwest_error("Poll request failed", err))?; + + let status = response.status(); + + if status.is_success() { + let task_response: TaskResponse = response + .json() + .map_err(|err| from_reqwest_error("Failed to parse task response", err))?; + + if task_response.code != 0 { + return Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + task_response.code, task_response.message + ))); + } + + match task_response.data.task_status.as_str() { + "submitted" | "processing" => Ok(PollResponse::Processing), + "succeed" => { + if let Some(task_result) = task_response.data.task_result { + if let Some(videos) = task_result.videos { + if let Some(video) = videos.first() { + // Download the video from the URL + let video_data = self.download_video(&video.url)?; + Ok(PollResponse::Complete { + video_data, + mime_type: "video/mp4".to_string(), + duration: video.duration.clone(), + }) + } else { + Err(VideoError::InternalError( + "No video in successful task".to_string(), + )) + } + } else { + Err(VideoError::InternalError( + "No videos in successful task".to_string(), + )) + } + } else { + Err(VideoError::InternalError( + "No task result in successful task".to_string(), + )) + } + } + "failed" => { + let error_msg = task_response + .data + .task_status_msg + .unwrap_or_else(|| "Task failed".to_string()); + Err(VideoError::GenerationFailed(error_msg)) + } + _ => Err(VideoError::InternalError(format!( + "Unknown task status: {}", + task_response.data.task_status + ))), + } + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + Err(video_error_from_status(status, error_body)) + } + } + + fn download_video(&self, url: &str) -> Result, VideoError> { + trace!("Downloading video from URL: {url}"); + + let response: Response = self + .client + .get(url) + .send() + .map_err(|err| from_reqwest_error("Failed to download video", err))?; + + if !response.status().is_success() { + return Err(VideoError::InternalError(format!( + "Failed to download video: HTTP {}", + response.status() + ))); + } + + let bytes = response + .bytes() + .map_err(|err| from_reqwest_error("Failed to read video data", err))?; + + Ok(bytes.to_vec()) + } + + pub fn generate_lip_sync( + &self, + request: LipSyncRequest, + ) -> Result { + trace!("Sending lip-sync request to Kling API"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/videos/lip-sync")) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Lip-sync request failed", err))?; + + parse_response(response) + } + + pub fn generate_video_effects( + &self, + request: VideoEffectsRequest, + ) -> Result { + trace!("Sending video effects request to Kling API"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/videos/effects")) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Video effects request failed", err))?; + + parse_response(response) + } + + pub fn extend_video( + &self, + request: VideoExtendRequest, + ) -> Result { + trace!("Sending video extend request to Kling API"); + + let auth_header = self.get_auth_header()?; + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/videos/video-extend")) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Video extend request failed", err))?; + + parse_response(response) + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct TextToVideoRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub model_name: Option, + pub prompt: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cfg_scale: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub camera_control: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub external_task_id: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ImageToVideoRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub model_name: Option, + pub prompt: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cfg_scale: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub image: Option, // Base64 encoded image or URL + #[serde(skip_serializing_if = "Option::is_none")] + pub image_tail: Option, // Base64 encoded image or URL + #[serde(skip_serializing_if = "Option::is_none")] + pub static_mask: Option, // Base64 encoded image or URL + #[serde(skip_serializing_if = "Option::is_none")] + pub dynamic_masks: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub camera_control: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub external_task_id: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DynamicMaskRequest { + pub mask: String, // Base64 encoded image or URL + pub trajectories: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct TrajectoryPoint { + pub x: u32, + pub y: u32, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CameraControlRequest { + #[serde(rename = "type")] + pub movement_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CameraConfigRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub horizontal: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub vertical: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pan: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tilt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub roll: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub zoom: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenerationResponse { + pub code: i32, + pub message: String, + pub request_id: String, + pub data: GenerationResponseData, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenerationResponseData { + pub task_id: String, + pub task_status: String, + pub task_info: TaskInfo, + pub created_at: u64, + pub updated_at: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TaskInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub external_task_id: Option, +} + +#[derive(Debug, Clone)] +pub enum PollResponse { + Processing, + Complete { + video_data: Vec, + mime_type: String, + duration: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TaskResponse { + pub code: i32, + pub message: String, + pub request_id: String, + pub data: TaskResponseData, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TaskResponseData { + pub task_id: String, + pub task_status: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub task_status_msg: Option, + pub task_info: TaskInfo, + pub created_at: u64, + pub updated_at: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub task_result: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TaskResult { + #[serde(skip_serializing_if = "Option::is_none")] + pub videos: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VideoResult { + pub id: String, + pub url: String, + pub duration: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct MultiImageToVideoRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub model_name: Option, + pub image_list: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub external_task_id: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ImageListItem { + pub image: String, // Base64 encoded image or URL +} + +#[derive(Debug, Clone, Serialize)] +pub struct LipSyncRequest { + pub input: LipSyncInput, + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct LipSyncInput { + #[serde(skip_serializing_if = "Option::is_none")] + pub video_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub video_url: Option, + pub mode: String, + // Text2Video mode fields + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub voice_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub voice_language: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub voice_speed: Option, + // Audio2Video mode fields + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_file: Option, // Base64 encoded + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_url: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct VideoEffectsRequest { + pub effect_scene: String, + pub input: VideoEffectsInput, + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub external_task_id: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct VideoEffectsInput { + #[serde(skip_serializing_if = "Option::is_none")] + pub model_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub image: Option, // Base64 encoded image or URL (for single image effects) + #[serde(skip_serializing_if = "Option::is_none")] + pub images: Option>, // Array of Base64 encoded images or URLs (for dual effects) + pub duration: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct VideoExtendRequest { + pub video_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cfg_scale: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, +} + +fn parse_response(response: Response) -> Result { + let status = response.status(); + if status.is_success() { + response + .json::() + .map_err(|err| from_reqwest_error("Failed to decode response body", err)) + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to receive error response body", err))?; + + let error_message = format!("Request failed with {status}: {error_body}"); + Err(video_error_from_status(status, error_message)) + } +} diff --git a/video/kling/src/conversion.rs b/video/kling/src/conversion.rs new file mode 100644 index 000000000..428cc08ec --- /dev/null +++ b/video/kling/src/conversion.rs @@ -0,0 +1,930 @@ +use crate::client::{ + CameraConfigRequest, CameraControlRequest, DynamicMaskRequest, ImageListItem, + ImageToVideoRequest, KlingApi, LipSyncInput, LipSyncRequest, MultiImageToVideoRequest, + PollResponse, TextToVideoRequest, TrajectoryPoint, VideoExtendRequest, +}; +use golem_video::error::invalid_input; +use golem_video::exports::golem::video::types::{ + AspectRatio, AudioSource, CameraControl, CameraMovement, GenerationConfig, ImageRole, + JobStatus, MediaData, MediaInput, Resolution, Video, VideoError, VideoResult, +}; +use log::trace; +use std::collections::HashMap; + +pub fn media_input_to_request( + input: MediaInput, + config: GenerationConfig, +) -> Result<(Option, Option), VideoError> { + // Parse provider options + let options: HashMap = config + .provider_options + .as_ref() + .map(|po| { + po.iter() + .map(|kv| (kv.key.clone(), kv.value.clone())) + .collect() + }) + .unwrap_or_default(); + + // Determine model - default to kling-v1, can be overridden + let model_name = config.model.clone().or_else(|| { + options + .get("model") + .cloned() + .or_else(|| Some("kling-v1".to_string())) + }); + + // Validate model if provided + if let Some(ref model) = model_name { + if !matches!( + model.as_str(), + "kling-v1" | "kling-v1-6" | "kling-v2-master" | "kling-v2-1-master" + ) { + return Err(invalid_input( + "Model must be one of: kling-v1, kling-v1-6, kling-v2-master, kling-v2-1-master", + )); + } + } + + // Determine aspect ratio + let aspect_ratio = determine_aspect_ratio(config.aspect_ratio, config.resolution)?; + + // Duration support - Kling supports 5 and 10 seconds + let duration = config.duration_seconds.map(|d| { + if d <= 5.0 { + "5".to_string() + } else { + "10".to_string() + } + }); + + // Mode support - std or pro + let mode = options + .get("mode") + .cloned() + .or_else(|| Some("std".to_string())); + if let Some(ref mode_val) = mode { + if !matches!(mode_val.as_str(), "std" | "pro") { + return Err(invalid_input("Mode must be 'std' or 'pro'")); + } + } + + // CFG scale support (0.0 to 1.0) + let cfg_scale = config + .guidance_scale + .map(|scale| (scale / 10.0).clamp(0.0, 1.0)); + + // Camera control support + let camera_control = config + .camera_control + .as_ref() + .map(convert_camera_control) + .transpose()?; + + // Clone negative_prompt before moving values + let negative_prompt = config.negative_prompt.clone(); + + match input { + MediaInput::Video(_) => Err(golem_video::error::unsupported_feature( + "Video-to-video is not supported by Kling API", + )), + MediaInput::Text(prompt) => { + let request = TextToVideoRequest { + model_name, + prompt, + negative_prompt, + cfg_scale, + mode, + camera_control, + aspect_ratio: Some(aspect_ratio), + duration, + callback_url: None, + external_task_id: None, + }; + + // Log warnings for unsupported options + log_unsupported_options(&config, &options); + + Ok((Some(request), None)) + } + MediaInput::Image(ref_image) => { + // Handle role and lastframe logic + let image_role = ref_image.role.as_ref(); + + // Check for conflict: both role=last and lastframe provided + if matches!(image_role, Some(ImageRole::Last)) && config.lastframe.is_some() { + log::warn!("Both image role=last and lastframe provided. Using lastframe only as specified."); + } + + // Extract image data from InputImage structure + let image_data = convert_media_data_to_string(&ref_image.data.data)?; + + // Handle lastframe - either from role=last or explicit lastframe + let image_tail = if matches!(image_role, Some(ImageRole::Last)) { + Some(image_data.clone()) + } else if let Some(ref lastframe) = config.lastframe { + Some(convert_media_data_to_string(&lastframe.data)?) + } else { + None + }; + + // Set image based on role - if role=last, use None for main image, otherwise use the image + let main_image = if matches!(image_role, Some(ImageRole::Last)) { + None + } else { + Some(image_data) + }; + + // Static mask support + let static_mask = config + .static_mask + .as_ref() + .map(|sm| convert_media_data_to_string(&sm.mask.data)) + .transpose()?; + + // Dynamic mask support + let dynamic_masks = config + .dynamic_mask + .as_ref() + .map(convert_dynamic_mask) + .transpose()?; + + // Validate API constraints: image+image_tail, dynamic_masks/static_mask, and camera_control cannot be used together + let has_image_tail = image_tail.is_some(); + let has_masks = static_mask.is_some() || dynamic_masks.is_some(); + let has_camera_control = camera_control.is_some(); + + if has_image_tail && has_masks { + return Err(invalid_input( + "image_tail (lastframe) cannot be used together with static_mask or dynamic_masks", + )); + } + if has_image_tail && has_camera_control { + return Err(invalid_input( + "image_tail (lastframe) cannot be used together with camera_control", + )); + } + if has_masks && has_camera_control { + return Err(invalid_input( + "static_mask/dynamic_masks cannot be used together with camera_control", + )); + } + + // Validate that at least one image (image or image_tail) is provided + if main_image.is_none() && image_tail.is_none() { + return Err(invalid_input( + "At least one of image or image_tail must be provided", + )); + } + + // Use prompt from the reference image, or default + let prompt = ref_image + .prompt + .clone() + .unwrap_or_else(|| "Generate a video from this image".to_string()); + + let request = ImageToVideoRequest { + model_name, + prompt, + negative_prompt, + cfg_scale, + mode, + aspect_ratio: Some(aspect_ratio), + duration, + image: main_image, + image_tail, + static_mask, + dynamic_masks, + camera_control, + callback_url: None, + external_task_id: None, + }; + + // Log warnings for unsupported options + log_unsupported_options(&config, &options); + + Ok((None, Some(request))) + } + } +} + +fn convert_media_data_to_string(media_data: &MediaData) -> Result { + match media_data { + MediaData::Url(url) => Ok(url.clone()), + MediaData::Bytes(raw_bytes) => { + // Convert bytes to base64 string + use base64::Engine; + Ok(base64::engine::general_purpose::STANDARD.encode(&raw_bytes.bytes)) + } + } +} + +fn convert_camera_control( + camera_control: &CameraControl, +) -> Result { + match camera_control { + CameraControl::Movement(movement) => { + let movement_type = match movement { + CameraMovement::Simple => "simple".to_string(), + CameraMovement::DownBack => "down_back".to_string(), + CameraMovement::ForwardUp => "forward_up".to_string(), + CameraMovement::RightTurnForward => "right_turn_forward".to_string(), + CameraMovement::LeftTurnForward => "left_turn_forward".to_string(), + }; + + Ok(CameraControlRequest { + movement_type, + config: None, + }) + } + CameraControl::Config(config) => { + // For simple camera control with custom config + // Validate that only one parameter is non-zero + let non_zero_count = [ + config.horizontal, + config.vertical, + config.pan, + config.tilt, + config.roll, + config.zoom, + ] + .iter() + .filter(|&&val| val != 0.0) + .count(); + + if non_zero_count != 1 { + return Err(invalid_input( + "Camera config must have exactly one non-zero parameter", + )); + } + + // Validate range [-10, 10] + for &val in &[ + config.horizontal, + config.vertical, + config.pan, + config.tilt, + config.roll, + config.zoom, + ] { + if !(-10.0..=10.0).contains(&val) { + return Err(invalid_input( + "Camera config values must be in range [-10, 10]", + )); + } + } + + let config_req = CameraConfigRequest { + horizontal: if config.horizontal != 0.0 { + Some(config.horizontal) + } else { + None + }, + vertical: if config.vertical != 0.0 { + Some(config.vertical) + } else { + None + }, + pan: if config.pan != 0.0 { + Some(config.pan) + } else { + None + }, + tilt: if config.tilt != 0.0 { + Some(config.tilt) + } else { + None + }, + roll: if config.roll != 0.0 { + Some(config.roll) + } else { + None + }, + zoom: if config.zoom != 0.0 { + Some(config.zoom) + } else { + None + }, + }; + + Ok(CameraControlRequest { + movement_type: "simple".to_string(), + config: Some(config_req), + }) + } + } +} + +fn convert_dynamic_mask( + dynamic_mask: &golem_video::exports::golem::video::types::DynamicMask, +) -> Result, VideoError> { + // Validate trajectory length (max 77 for 5s video) + if dynamic_mask.trajectories.len() < 2 { + return Err(invalid_input( + "Dynamic mask must have at least 2 trajectory points", + )); + } + if dynamic_mask.trajectories.len() > 77 { + return Err(invalid_input( + "Dynamic mask cannot have more than 77 trajectory points", + )); + } + + let mask_data = convert_media_data_to_string(&dynamic_mask.mask.data)?; + let trajectories: Vec = dynamic_mask + .trajectories + .iter() + .map(|pos| TrajectoryPoint { x: pos.x, y: pos.y }) + .collect(); + + Ok(vec![DynamicMaskRequest { + mask: mask_data, + trajectories, + }]) +} + +fn determine_aspect_ratio( + aspect_ratio: Option, + _resolution: Option, +) -> Result { + let target_aspect = aspect_ratio.unwrap_or(AspectRatio::Landscape); + + match target_aspect { + AspectRatio::Landscape => Ok("16:9".to_string()), + AspectRatio::Portrait => Ok("9:16".to_string()), + AspectRatio::Square => Ok("1:1".to_string()), + AspectRatio::Cinema => { + log::warn!("Cinema aspect ratio not directly supported, using 16:9"); + Ok("16:9".to_string()) + } + } +} + +fn log_unsupported_options(config: &GenerationConfig, options: &HashMap) { + if config.scheduler.is_some() { + log::warn!("scheduler is not supported by Kling API and will be ignored"); + } + if config.enable_audio.is_some() { + log::warn!("enable_audio is not supported by Kling API and will be ignored"); + } + if config.enhance_prompt.is_some() { + log::warn!("enhance_prompt is not supported by Kling API and will be ignored"); + } + + // Log unused provider options + for key in options.keys() { + if !matches!(key.as_str(), "model" | "mode") { + log::warn!("Provider option '{key}' is not supported by Kling API"); + } + } +} + +fn log_multi_image_unsupported_options( + config: &GenerationConfig, + options: &HashMap, +) { + // Multi-image generation has additional restrictions + if config.scheduler.is_some() { + log::warn!("scheduler is not supported by Kling multi-image API and will be ignored"); + } + if config.enable_audio.is_some() { + log::warn!("enable_audio is not supported by Kling multi-image API and will be ignored"); + } + if config.enhance_prompt.is_some() { + log::warn!("enhance_prompt is not supported by Kling multi-image API and will be ignored"); + } + if config.guidance_scale.is_some() { + log::warn!("guidance_scale (cfg_scale) is not supported by Kling multi-image API and will be ignored"); + } + if config.lastframe.is_some() { + log::warn!("lastframe is not supported by Kling multi-image API and will be ignored"); + } + if config.static_mask.is_some() { + log::warn!("static_mask is not supported by Kling multi-image API and will be ignored"); + } + if config.dynamic_mask.is_some() { + log::warn!("dynamic_mask is not supported by Kling multi-image API and will be ignored"); + } + if config.camera_control.is_some() { + log::warn!("camera_control is not supported by Kling multi-image API and will be ignored"); + } + + // Log unused provider options + for key in options.keys() { + if !matches!(key.as_str(), "model" | "mode") { + log::warn!("Provider option '{key}' is not supported by Kling multi-image API"); + } + } +} + +pub fn generate_video( + client: &KlingApi, + input: MediaInput, + config: GenerationConfig, +) -> Result { + let (text_request, image_request) = media_input_to_request(input, config)?; + + if let Some(request) = text_request { + let response = client.generate_text_to_video(request)?; + if response.code == 0 { + Ok(response.data.task_id) + } else { + Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + response.code, response.message + ))) + } + } else if let Some(request) = image_request { + let response = client.generate_image_to_video(request)?; + if response.code == 0 { + Ok(response.data.task_id) + } else { + Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + response.code, response.message + ))) + } + } else { + Err(VideoError::InternalError( + "No valid request generated".to_string(), + )) + } +} + +pub fn poll_video_generation( + client: &KlingApi, + task_id: String, +) -> Result { + match client.poll_generation(&task_id) { + Ok(PollResponse::Processing) => Ok(VideoResult { + status: JobStatus::Running, + videos: None, + metadata: None, + }), + Ok(PollResponse::Complete { + video_data, + mime_type, + duration, + }) => { + // Parse duration to extract seconds if possible + let duration_seconds = parse_duration_string(&duration); + + let video = Video { + uri: None, + base64_bytes: Some(video_data), + mime_type, + width: None, + height: None, + fps: None, + duration_seconds, + }; + + Ok(VideoResult { + status: JobStatus::Succeeded, + videos: Some(vec![video]), + metadata: None, + }) + } + Err(error) => Err(error), + } +} + +fn parse_duration_string(duration_str: &str) -> Option { + // Try to parse duration string like "5" or "10" to float + duration_str.parse::().ok() +} + +pub fn cancel_video_generation(_client: &KlingApi, task_id: String) -> Result { + // Kling API does not support cancellation according to requirements + Err(VideoError::UnsupportedFeature(format!( + "Cancellation is not supported by Kling API for task {task_id}" + ))) +} + +pub fn generate_lip_sync_video( + client: &KlingApi, + video: golem_video::exports::golem::video::types::BaseVideo, + audio: golem_video::exports::golem::video::types::AudioSource, +) -> Result { + trace!("Generating lip-sync video with Kling API"); + + // Convert video data to required format + let (video_id, video_url) = match &video.data { + MediaData::Url(url) => (None, Some(url.clone())), + MediaData::Bytes(_) => { + return Err(invalid_input( + "Lip-sync requires video URL or video_id from Kling API. Base64 video data is not supported.", + )); + } + }; + + // Convert audio source to request format + let (mode, text, voice_id, voice_language, voice_speed, audio_type, audio_file, audio_url) = + match audio { + AudioSource::FromText(tts) => { + // Text-to-video mode + let voice_id = &tts.voice_id; + + // Use the language from the TTS object + let language = match tts.language { + golem_video::exports::golem::video::types::VoiceLanguage::En => "en", + golem_video::exports::golem::video::types::VoiceLanguage::Zh => "zh", + }; + + // Convert speed from u32 to f32 and validate range + let speed = tts.speed as f32 / 100.0; // Convert from percentage to decimal + let voice_speed = speed.clamp(0.8, 2.0); + + ( + "text2video".to_string(), + Some(tts.text.clone()), + Some(voice_id.clone()), + Some(language.to_string()), + Some(voice_speed), + None, + None, + None, + ) + } + AudioSource::FromAudio(narration) => { + // Audio-to-video mode + match &narration.data { + MediaData::Url(url) => ( + "audio2video".to_string(), + None, + None, + None, + None, + Some("url".to_string()), + None, + Some(url.clone()), + ), + MediaData::Bytes(raw_bytes) => { + // Convert to base64 + use base64::Engine; + let audio_base64 = + base64::engine::general_purpose::STANDARD.encode(&raw_bytes.bytes); + ( + "audio2video".to_string(), + None, + None, + None, + None, + Some("file".to_string()), + Some(audio_base64), + None, + ) + } + } + } + }; + + let input = LipSyncInput { + video_id, + video_url, + mode, + text, + voice_id, + voice_language, + voice_speed, + audio_type, + audio_file, + audio_url, + }; + + let request = LipSyncRequest { + input, + callback_url: None, + }; + + let response = client.generate_lip_sync(request)?; + if response.code == 0 { + Ok(response.data.task_id) + } else { + Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + response.code, response.message + ))) + } +} + +pub fn list_available_voices( + _client: &KlingApi, + language: Option, +) -> Result, VideoError> { + use crate::voices::get_voices; + + trace!("Listing available voices for language: {language:?}"); + + let voices = get_voices(language); + Ok(voices) +} + +pub fn extend_video( + client: &KlingApi, + video_id: String, + prompt: Option, + negative_prompt: Option, + cfg_scale: Option, + provider_options: Option>, +) -> Result { + trace!("Extending video with ID: {video_id}"); + + // Parse provider options + let options: HashMap = provider_options + .as_ref() + .map(|po| { + po.iter() + .map(|kv| (kv.key.clone(), kv.value.clone())) + .collect() + }) + .unwrap_or_default(); + + // Validate prompt length (max 2500 characters) + if let Some(ref p) = prompt { + if p.len() > 2500 { + return Err(invalid_input("Prompt cannot exceed 2500 characters")); + } + } + + // Validate negative prompt length (max 2500 characters) + if let Some(ref np) = negative_prompt { + if np.len() > 2500 { + return Err(invalid_input( + "Negative prompt cannot exceed 2500 characters", + )); + } + } + + // Validate cfg_scale range [0, 1] + if let Some(scale) = cfg_scale { + if !(0.0..=1.0).contains(&scale) { + return Err(invalid_input("cfg_scale must be between 0.0 and 1.0")); + } + } + + // Log warnings for unsupported provider options + for key in options.keys() { + log::warn!("Provider option '{key}' is not supported by Kling video extension API"); + } + + let request = VideoExtendRequest { + video_id, + prompt, + negative_prompt, + cfg_scale, + callback_url: None, + }; + + let response = client.extend_video(request)?; + if response.code == 0 { + Ok(response.data.task_id) + } else { + Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + response.code, response.message + ))) + } +} + +pub fn upscale_video( + _client: &KlingApi, + _input: golem_video::exports::golem::video::types::BaseVideo, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Video upscaling is not supported by Kling API".to_string(), + )) +} + +pub fn generate_video_effects( + client: &KlingApi, + input: golem_video::exports::golem::video::types::InputImage, + effect: golem_video::exports::golem::video::types::EffectType, + model: Option, + duration: Option, + mode: Option, +) -> Result { + use crate::client::{VideoEffectsInput, VideoEffectsRequest}; + use golem_video::exports::golem::video::types::{ + DualImageEffects, EffectType, SingleImageEffects, + }; + + trace!("Generating video effects with Kling API"); + + // Convert input image to string (Base64 or URL) + let input_image_data = convert_media_data_to_string(&input.data)?; + + // Determine effect scene and build request based on effect type + let (effect_scene, request_input) = match effect { + EffectType::Single(single_effect) => { + // Single image effects + let scene_name = match single_effect { + SingleImageEffects::Bloombloom => "bloombloom", + SingleImageEffects::Dizzydizzy => "dizzydizzy", + SingleImageEffects::Fuzzyfuzzy => "fuzzyfuzzy", + SingleImageEffects::Squish => "squish", + SingleImageEffects::Expansion => "expansion", + }; + + // For single image effects, model_name is required to be "kling-v1-6" + let model_name = Some("kling-v1-6".to_string()); + + // Duration for single image effects is fixed to "5" + let duration_str = "5".to_string(); + + // Single image effects don't support mode parameter + if mode.is_some() { + log::warn!( + "Mode parameter is not supported for single image effects and will be ignored" + ); + } + + let input = VideoEffectsInput { + model_name, + mode: None, // Single image effects don't support mode + image: Some(input_image_data), + images: None, + duration: duration_str, + }; + + (scene_name.to_string(), input) + } + EffectType::Dual(dual_effect) => { + // Dual character effects + let scene_name = match dual_effect.effect { + DualImageEffects::Hug => "hug", + DualImageEffects::Kiss => "kiss", + DualImageEffects::HeartGesture => "heart_gesture", + }; + + // Convert second image to string + let second_image_data = convert_media_data_to_string(&dual_effect.second_image.data)?; + + // Build images array with first and second image + let images = vec![input_image_data, second_image_data]; + + // For dual effects, model validation + let model_name = if let Some(ref m) = model { + if !matches!(m.as_str(), "kling-v1" | "kling-v1-5" | "kling-v1-6") { + return Err(invalid_input( + "Model must be one of: kling-v1, kling-v1-5, kling-v1-6 for dual effects", + )); + } + Some(m.clone()) + } else { + Some("kling-v1".to_string()) // Default for dual effects + }; + + // Mode validation + let mode_val = if let Some(ref m) = mode { + if !matches!(m.as_str(), "std" | "pro") { + return Err(invalid_input("Mode must be 'std' or 'pro'")); + } + Some(m.clone()) + } else { + Some("std".to_string()) // Default mode + }; + + // Duration handling - convert from seconds to string + let duration_str = if let Some(dur) = duration { + if dur <= 5.0 { + "5".to_string() + } else { + "10".to_string() + } + } else { + "5".to_string() // Default duration + }; + + let input = VideoEffectsInput { + model_name, + mode: mode_val, + image: None, // For dual effects, use images array instead + images: Some(images), + duration: duration_str, + }; + + (scene_name.to_string(), input) + } + }; + + let request = VideoEffectsRequest { + effect_scene, + input: request_input, + callback_url: None, + external_task_id: None, + }; + + let response = client.generate_video_effects(request)?; + if response.code == 0 { + Ok(response.data.task_id) + } else { + Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + response.code, response.message + ))) + } +} + +pub fn multi_image_generation( + client: &KlingApi, + input_images: Vec, + prompt: Option, + config: GenerationConfig, +) -> Result { + // Validate input: 1 to 4 images supported + if input_images.is_empty() { + return Err(invalid_input( + "At least 1 image is required for multi-image generation", + )); + } + if input_images.len() > 4 { + return Err(invalid_input( + "Multi-image generation supports at most 4 images", + )); + } + + // Parse provider options + let options: HashMap = config + .provider_options + .as_ref() + .map(|po| { + po.iter() + .map(|kv| (kv.key.clone(), kv.value.clone())) + .collect() + }) + .unwrap_or_default(); + + // Determine model - for multi-image, default to kling-v1-6 as per API docs + let model_name = config.model.clone().or_else(|| { + options + .get("model") + .cloned() + .or_else(|| Some("kling-v1-6".to_string())) + }); + + // Validate model if provided (multi-image endpoint only supports kling-v1-6 according to docs) + if let Some(ref model) = model_name { + if model != "kling-v1-6" { + log::warn!("Multi-image generation only supports kling-v1-6 model. Using kling-v1-6."); + } + } + + // Convert input images to image_list format + let mut image_list = Vec::new(); + for input_image in &input_images { + let image_data = convert_media_data_to_string(&input_image.data)?; + image_list.push(ImageListItem { image: image_data }); + } + + // Build prompt - use the first image's prompt if available, or create a default + let prompt = prompt.unwrap_or_else(|| "Generate a video from these images".to_string()); + + // Determine aspect ratio + let aspect_ratio = determine_aspect_ratio(config.aspect_ratio, config.resolution)?; + + // Duration support - Kling supports 5 and 10 seconds + let duration = config.duration_seconds.map(|d| { + if d <= 5.0 { + "5".to_string() + } else { + "10".to_string() + } + }); + + // Mode support - std or pro + let mode = options + .get("mode") + .cloned() + .or_else(|| Some("std".to_string())); + if let Some(ref mode_val) = mode { + if !matches!(mode_val.as_str(), "std" | "pro") { + return Err(invalid_input("Mode must be 'std' or 'pro'")); + } + } + + let request = MultiImageToVideoRequest { + model_name: Some("kling-v1-6".to_string()), // Force kling-v1-6 for multi-image + image_list, + prompt: Some(prompt), + negative_prompt: config.negative_prompt.clone(), + mode, + duration, + aspect_ratio: Some(aspect_ratio), + callback_url: None, + external_task_id: None, + }; + + // Log warnings for unsupported options specific to multi-image + log_multi_image_unsupported_options(&config, &options); + + let response = client.generate_multi_image_to_video(request)?; + if response.code == 0 { + Ok(response.data.task_id) + } else { + Err(VideoError::GenerationFailed(format!( + "API error {}: {}", + response.code, response.message + ))) + } +} diff --git a/video/kling/src/lib.rs b/video/kling/src/lib.rs new file mode 100644 index 000000000..d51b92418 --- /dev/null +++ b/video/kling/src/lib.rs @@ -0,0 +1,162 @@ +mod authentication; +mod client; +mod conversion; +mod voices; + +use crate::client::KlingApi; +use crate::conversion::{ + cancel_video_generation, extend_video, generate_lip_sync_video, generate_video, + generate_video_effects, list_available_voices, multi_image_generation, poll_video_generation, + upscale_video, +}; +use golem_video::config::with_config_key; +use golem_video::durability::{DurableVideo, ExtendedGuest}; +use golem_video::exports::golem::video::advanced::Guest as AdvancedGuest; +use golem_video::exports::golem::video::lip_sync::Guest as LipSyncGuest; +use golem_video::exports::golem::video::types::{ + AudioSource, BaseVideo, EffectType, GenerationConfig, InputImage, Kv, MediaInput, VideoError, + VideoResult, VoiceInfo, +}; +use golem_video::exports::golem::video::video_generation::Guest as VideoGenerationGuest; +use golem_video::LOGGING_STATE; + +struct KlingComponent; + +impl KlingComponent { + const ACCESS_KEY_ENV_VAR: &'static str = "KLING_ACCESS_KEY"; + const SECRET_KEY_ENV_VAR: &'static str = "KLING_SECRET_KEY"; +} + +impl VideoGenerationGuest for KlingComponent { + fn generate(input: MediaInput, config: GenerationConfig) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + generate_video(&client, input, config) + }) + }) + } + + fn poll(job_id: String) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + poll_video_generation(&client, job_id) + }) + }) + } + + fn cancel(job_id: String) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + cancel_video_generation(&client, job_id) + }) + }) + } +} + +impl LipSyncGuest for KlingComponent { + fn generate_lip_sync(video: BaseVideo, audio: AudioSource) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + generate_lip_sync_video(&client, video, audio) + }) + }) + } + + fn list_voices(language: Option) -> Result, VideoError> { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + list_available_voices(&client, language) + }) + }) + } +} + +impl AdvancedGuest for KlingComponent { + fn extend_video( + video_id: String, + prompt: Option, + negative_prompt: Option, + cfg_scale: Option, + provider_options: Option>, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + extend_video( + &client, + video_id, + prompt, + negative_prompt, + cfg_scale, + provider_options, + ) + }) + }) + } + + fn upscale_video(input: BaseVideo) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + upscale_video(&client, input) + }) + }) + } + + fn generate_video_effects( + input: InputImage, + effect: EffectType, + model: Option, + duration: Option, + mode: Option, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + generate_video_effects(&client, input, effect, model, duration, mode) + }) + }) + } + + fn multi_image_generation( + input_images: Vec, + prompt: Option, + config: GenerationConfig, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + + with_config_key(Self::ACCESS_KEY_ENV_VAR, Err, |access_key| { + with_config_key(Self::SECRET_KEY_ENV_VAR, Err, |secret_key| { + let client = KlingApi::new(access_key, secret_key); + multi_image_generation(&client, input_images, prompt, config) + }) + }) + } +} + +impl ExtendedGuest for KlingComponent {} + +type DurableKlingComponent = DurableVideo; + +golem_video::export_video!(DurableKlingComponent with_types_in golem_video); diff --git a/video/kling/src/voices.rs b/video/kling/src/voices.rs new file mode 100644 index 000000000..1d735b606 --- /dev/null +++ b/video/kling/src/voices.rs @@ -0,0 +1,402 @@ +use golem_video::exports::golem::video::types::{VoiceInfo, VoiceLanguage}; + +/// Voice data for Kling lip-sync functionality +/// Data sourced from Kling API documentation +/// https://docs.qingque.cn/s/home/eZQDvafJ4vXQkP8T9ZPvmye8S?identityId=2E3S0NySBQy +/// It isnt possible to do this dynamically, or have preview urls +fn get_chinese_voices() -> Vec { + vec![ + VoiceInfo { + voice_id: "genshin_vindi2".to_string(), + name: "阳光少年".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "zhinen_xuesheng".to_string(), + name: "懂事小弟".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "tiyuxi_xuedi".to_string(), + name: "运动少年".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_shatang".to_string(), + name: "青春少女".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "genshin_klee2".to_string(), + name: "温柔小妹".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "genshin_kirara".to_string(), + name: "元气少女".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_kaiya".to_string(), + name: "阳光男生".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "tiexin_nanyou".to_string(), + name: "幽默小哥".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_chenjiahao_712".to_string(), + name: "文艺小哥".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "girlfriend_1_speech02".to_string(), + name: "甜美邻家".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "chat1_female_new-3".to_string(), + name: "温柔姐姐".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "girlfriend_2_speech02".to_string(), + name: "职场女青".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "cartoon-boy-07".to_string(), + name: "活泼男童".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "cartoon-girl-01".to_string(), + name: "俏皮女童".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_huangyaoshi_712".to_string(), + name: "稳重老爸".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "you_pingjing".to_string(), + name: "温柔妈妈".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_laoguowang_712".to_string(), + name: "严肃上司".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "chengshu_jiejie".to_string(), + name: "优雅贵妇".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "zhuxi_speech02".to_string(), + name: "慈祥爷爷".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "uk_oldman3".to_string(), + name: "唠叨爷爷".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "laopopo_speech02".to_string(), + name: "唠叨奶奶".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "heainainai_speech02".to_string(), + name: "和蔼奶奶".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "dongbeilaotie_speech02".to_string(), + name: "东北老铁".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "chongqingxiaohuo_speech02".to_string(), + name: "重庆小伙".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "chuanmeizi_speech02".to_string(), + name: "四川妹子".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "chaoshandashu_speech02".to_string(), + name: "潮汕大叔".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_taiwan_man2_speech02".to_string(), + name: "台湾男生".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "xianzhanggui_speech02".to_string(), + name: "西安掌柜".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "tianjinjiejie_speech02".to_string(), + name: "天津姐姐".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "diyinnansang_DB_CN_M_04-v2".to_string(), + name: "新闻播报男".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "yizhipiannan-v1".to_string(), + name: "译制片男".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "guanxiaofang-v2".to_string(), + name: "元气少女".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "tianmeixuemei-v1".to_string(), + name: "撒娇女友".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "daopianyansang-v1".to_string(), + name: "刀片烟嗓".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + VoiceInfo { + voice_id: "mengwa-v1".to_string(), + name: "乖巧正太".to_string(), + language: VoiceLanguage::Zh, + preview_url: None, + }, + ] +} + +fn get_english_voices() -> Vec { + vec![ + VoiceInfo { + voice_id: "genshin_vindi2".to_string(), + name: "Sunny".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "zhinen_xuesheng".to_string(), + name: "Sage".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "AOT".to_string(), + name: "Ace".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_shatang".to_string(), + name: "Blossom".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "genshin_klee2".to_string(), + name: "Peppy".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "genshin_kirara".to_string(), + name: "Dove".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_kaiya".to_string(), + name: "Shine".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "oversea_male1".to_string(), + name: "Anchor".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_chenjiahao_712".to_string(), + name: "Lyric".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "girlfriend_4_speech02".to_string(), + name: "Melody".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "chat1_female_new-3".to_string(), + name: "Tender".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "chat_0407_5-1".to_string(), + name: "Siren".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "cartoon-boy-07".to_string(), + name: "Zippy".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "uk_boy1".to_string(), + name: "Bud".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "cartoon-girl-01".to_string(), + name: "Sprite".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "PeppaPig_platform".to_string(), + name: "Candy".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_huangzhong_712".to_string(), + name: "Beacon".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_huangyaoshi_712".to_string(), + name: "Rock".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "ai_laoguowang_712".to_string(), + name: "Titan".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "chengshu_jiejie".to_string(), + name: "Grace".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "you_pingjing".to_string(), + name: "Helen".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "calm_story1".to_string(), + name: "Lore".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "uk_man2".to_string(), + name: "Crag".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "laopopo_speech02".to_string(), + name: "Prattle".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "heainainai_speech02".to_string(), + name: "Hearth".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "reader_en_m-v1".to_string(), + name: "The Reader".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + VoiceInfo { + voice_id: "commercial_lady_en_f-v1".to_string(), + name: "Commercial Lady".to_string(), + language: VoiceLanguage::En, + preview_url: None, + }, + ] +} + +/// Get all voices for a specific language, or all voices if language is None +pub fn get_voices(language: Option) -> Vec { + match language.as_deref() { + Some("zh") => get_chinese_voices(), + Some("en") => get_english_voices(), + Some(_) => Vec::new(), // Unknown language + None => { + let mut all_voices = Vec::new(); + all_voices.extend(get_chinese_voices()); + all_voices.extend(get_english_voices()); + all_voices + } + } +} diff --git a/video/kling/wit/deps/golem-video/golem-video.wit b/video/kling/wit/deps/golem-video/golem-video.wit new file mode 100644 index 000000000..8a2ecf138 --- /dev/null +++ b/video/kling/wit/deps/golem-video/golem-video.wit @@ -0,0 +1,256 @@ +package golem:video@1.0.0; + +interface types { + variant video-error { + invalid-input(string), + unsupported-feature(string), + quota-exceeded, + generation-failed(string), + cancelled, + internal-error(string), + } + + variant media-input { + text(string), + image(reference), + video(base-video), + } + + record reference { + data: input-image, + prompt: option, + role: option, + } + + enum image-role { + first, + last, + } + + record input-image { + data: media-data, + } + + record base-video { + data: media-data, + } + + record narration { + data: media-data, + } + + variant media-data { + url(string), + bytes(raw-bytes), + } + + record raw-bytes { + bytes: list, + mime-type: string, + } + + record static-mask { + mask: input-image, + } + + record dynamic-mask { + mask: input-image, + trajectories: list, + } + + record position { + x: u32, + y: u32, + } + + enum camera-movement { + simple, + down-back, + forward-up, + right-turn-forward, + left-turn-forward, + } + + record camera-config { + horizontal: f32, + vertical: f32, + pan: f32, + tilt: f32, + zoom: f32, + roll: f32, + } + + variant camera-control { + movement(camera-movement), + config(camera-config), + } + + record generation-config { + negative-prompt: option, + seed: option, + scheduler: option, + guidance-scale: option, + aspect-ratio: option, + duration-seconds: option, + resolution: option, + model: option, + enable-audio: option, + enhance-prompt: option, + provider-options: option>, + lastframe: option, + static-mask: option, + dynamic-mask: option, + camera-control: option, + } + + enum aspect-ratio { + square, + portrait, + landscape, + cinema, + } + + enum resolution { + sd, + hd, + fhd, + uhd, + } + + record kv { + key: string, + value: string, + } + + record video { + uri: option, + base64-bytes: option>, + mime-type: string, + width: option, + height: option, + fps: option, + duration-seconds: option, + } + + variant job-status { + pending, + running, + succeeded, + failed(string), + } + + record video-result { + status: job-status, + videos: option>, + metadata: option>, + } + + record text-to-speech { + text: string, + voice-id: string, + language: voice-language, + speed: u32, + } + + variant audio-source { + from-text(text-to-speech), + from-audio(narration), + } + + record voice-info { + voice-id: string, + name: string, + language: voice-language, + preview-url: option, + } + + enum voice-language { + en, + zh + } + + enum single-image-effects { + bloombloom, + dizzydizzy, + fuzzyfuzzy, + squish, + expansion, + } + + enum dual-image-effects { + hug, + kiss, + heart-gesture, + } + + record dual-effect { + effect: dual-image-effects, + second-image: input-image, + } + + variant effect-type { + single(single-image-effects), + dual(dual-effect), + } +} + +interface video-generation { + use types.{media-input, generation-config, video-result, video-error}; + + generate: func(input: media-input, config: generation-config) -> result; + poll: func(job-id: string) -> result; + cancel: func(job-id: string) -> result; +} + +interface lip-sync { + use types.{base-video, audio-source, video-error, voice-info}; + + generate-lip-sync: func( + video: base-video, + audio: audio-source, + ) -> result; + + list-voices: func(language: option) -> result, video-error>; +} + +interface advanced { + use types.{video-error, kv, base-video, generation-config, input-image, effect-type}; + + extend-video: func( + video-id: string, + prompt: option, + negative-prompt: option, + cfg-scale: option, + provider-options: option>, + ) -> result; + + upscale-video: func( + input: base-video, + ) -> result; + + generate-video-effects: func( + input: input-image, + effect: effect-type, + model: option, + duration: option, + mode: option, + ) -> result; + + multi-image-generation: func( + input-images: list, + prompt: option, + config: generation-config, + ) -> result; +} + +world video-library { + import types; + import video-generation; + import lip-sync; + import advanced; + + export video-generation; + export lip-sync; + export advanced; + export types; +} diff --git a/video/kling/wit/kling.wit b/video/kling/wit/kling.wit new file mode 100644 index 000000000..846a1d570 --- /dev/null +++ b/video/kling/wit/kling.wit @@ -0,0 +1,5 @@ +package golem:video-kling@1.0.0; + +world video-library { + include golem:video/video-library@1.0.0; +} \ No newline at end of file diff --git a/video/runway/Cargo.toml b/video/runway/Cargo.toml new file mode 100644 index 000000000..3a0d23da9 --- /dev/null +++ b/video/runway/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "golem-video-runway" +version = "0.0.0" +edition = "2021" +license = "Apache-2.0" +homepage = "https://golem.cloud" +repository = "https://github.com/golemcloud/golem-llm" +description = "WebAssembly component for working with Runway video APIs, with special support for Golem Cloud" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["durability"] +durability = ["golem-rust/durability", "golem-video/durability"] + +[dependencies] +golem-video = { path = "../video", version = "0.0.0", default-features = false } + +golem-rust = { workspace = true } +log = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +wit-bindgen-rt = { workspace = true } +base64 = { workspace = true } + +[package.metadata.component] +package = "golem:video-runway" + +[package.metadata.component.bindings] +generate_unused_types = true + +[package.metadata.component.bindings.with] +"golem:video/types@1.0.0" = "golem_video::golem::video::types" +"golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +"golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +"golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" + +[package.metadata.component.target] +path = "wit" + +[package.metadata.component.target.dependencies] +"golem:video" = { path = "wit/deps/golem-video" } \ No newline at end of file diff --git a/video/runway/src/bindings.rs b/video/runway/src/bindings.rs new file mode 100644 index 000000000..b06f6c47d --- /dev/null +++ b/video/runway/src/bindings.rs @@ -0,0 +1,148 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +// * runtime_path: "wit_bindgen_rt" +// * with "golem:video/types@1.0.0" = "golem_video::golem::video::types" +// * with "golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" +// * with "golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +// * with "golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +// * generate_unused_types +use golem_video::golem::video::types as __with_name0; +use golem_video::golem::video::video_generation as __with_name1; +use golem_video::golem::video::lip_sync as __with_name2; +use golem_video::golem::video::advanced as __with_name3; +use golem_video::golem::video::types as __with_name4; +use golem_video::golem::video::video_generation as __with_name5; +use golem_video::golem::video::lip_sync as __with_name6; +use golem_video::golem::video::advanced as __with_name7; +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:video-runway@1.0.0:video-library:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 5482] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe6)\x01A\x02\x01A\x1a\ +\x01BO\x01q\x06\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-e\ +xceeded\0\0\x11generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\ +\x04\0\x0bvideo-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\ +\x02\x01p}\x01r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\ +\x02\x03url\x01s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04\ +data\x08\x04\0\x0binput-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06p\ +rompt\x0b\x04role\x0c\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0a\ +base-video\x03\0\x0f\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\ +\0\x04\0\x0bmedia-input\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\ +\x01r\x01\x04mask\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08\ +position\x03\0\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdy\ +namic-mask\x03\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn\ +-forward\x11left-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahor\ +izontalv\x08verticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\ +\0\x1e\x01q\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-contro\ +l\x03\0\x20\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspec\ +t-ratio\x03\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01\ +r\x02\x03keys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p\ +'\x01k-\x01k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed\ +(\x09scheduler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0ar\ +esolution+\x05model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\ +\x09lastframe/\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11gene\ +ration-config\x03\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime\ +-types\x05width6\x06height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01\ +q\x04\x07pending\0\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0aj\ +ob-status\x03\09\x01p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0c\ +video-result\x03\0=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\ +\x04texts\x08voice-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0\ +A\x01q\x02\x09from-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-sourc\ +e\x03\0C\x01r\x04\x08voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\ +\0\x0avoice-info\x03\0E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06sq\ +uish\x09expansion\x04\0\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0d\ +heart-gesture\x04\0\x12dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csec\ +ond-image\x0a\x04\0\x0bdual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\ +\x01\xcc\0\0\x04\0\x0beffect-type\x03\0M\x03\0\x17golem:video/types@1.0.0\x05\0\x02\ +\x03\0\0\x0bmedia-input\x02\x03\0\0\x11generation-config\x02\x03\0\0\x0cvideo-re\ +sult\x02\x03\0\0\x0bvideo-error\x01B\x10\x02\x03\x02\x01\x01\x04\0\x0bmedia-inpu\ +t\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\x03\0\x02\x02\x03\x02\x01\ +\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\ +\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06config\x03\0\x08\x04\0\x08gene\ +rate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\x0a\x04\0\x04poll\x01\x0b\ +\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x03\0\"golem:video/video-gene\ +ration@1.0.0\x05\x05\x02\x03\0\0\x0abase-video\x02\x03\0\0\x0caudio-source\x02\x03\ +\0\0\x0avoice-info\x01B\x10\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\0\x02\x03\ +\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\x02\x01\x04\x04\0\x0bvideo-\ +error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-info\x03\0\x06\x01j\x01s\x01\x05\ +\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11generate-lip-sync\x01\x09\x01\ +ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08language\x0a\0\x0c\x04\0\x0blist-vo\ +ices\x01\x0d\x03\0\x1agolem:video/lip-sync@1.0.0\x05\x09\x02\x03\0\0\x02kv\x02\x03\ +\0\0\x0binput-image\x02\x03\0\0\x0beffect-type\x01B\x1a\x02\x03\x02\x01\x04\x04\0\ +\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\ +\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\x02\x01\x02\x04\0\x11generation-confi\ +g\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0binput-image\x03\0\x08\x02\x03\x02\x01\x0c\ +\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01\ +@\x05\x08video-ids\x06prompt\x0c\x0fnegative-prompt\x0c\x09cfg-scale\x0d\x10prov\ +ider-options\x0f\0\x10\x04\0\x0cextend-video\x01\x11\x01@\x01\x05input\x05\0\x10\ +\x04\0\x0dupscale-video\x01\x12\x01@\x05\x05input\x09\x06effect\x0b\x05model\x0c\ +\x08duration\x0d\x04mode\x0c\0\x10\x04\0\x16generate-video-effects\x01\x13\x01p\x09\ +\x01@\x03\x0cinput-images\x14\x06prompt\x0c\x06config\x07\0\x10\x04\0\x16multi-i\ +mage-generation\x01\x15\x03\0\x1agolem:video/advanced@1.0.0\x05\x0d\x01BO\x01q\x06\ +\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-exceeded\0\0\x11\ +generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\x04\0\x0bvide\ +o-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\x02\x01p}\x01\ +r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\x02\x03url\x01\ +s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04data\x08\x04\0\x0b\ +input-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06prompt\x0b\x04role\x0c\ +\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0abase-video\x03\0\x0f\ +\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\0\x04\0\x0bmedia-in\ +put\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\x01r\x01\x04mask\ +\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08position\x03\0\ +\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdynamic-mask\x03\ +\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn-forward\x11le\ +ft-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahorizontalv\x08ve\ +rticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\0\x1e\x01q\ +\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-control\x03\0\x20\ +\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspect-ratio\x03\ +\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01r\x02\x03k\ +eys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p'\x01k-\x01\ +k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed(\x09schedu\ +ler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0aresolution+\x05\ +model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\x09lastframe/\ +\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11generation-config\x03\ +\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime-types\x05width6\x06\ +height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01q\x04\x07pending\0\ +\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0ajob-status\x03\09\x01\ +p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0cvideo-result\x03\0\ +=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\x04texts\x08voice\ +-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0A\x01q\x02\x09from\ +-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-source\x03\0C\x01r\x04\x08\ +voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\0\x0avoice-info\x03\0\ +E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06squish\x09expansion\x04\0\ +\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0dheart-gesture\x04\0\x12\ +dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csecond-image\x0a\x04\0\x0b\ +dual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\x01\xcc\0\0\x04\0\x0be\ +ffect-type\x03\0M\x04\0\x17golem:video/types@1.0.0\x05\x0e\x01B\x10\x02\x03\x02\x01\ +\x01\x04\0\x0bmedia-input\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\ +\x03\0\x02\x02\x03\x02\x01\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\ +\x04\0\x0bvideo-error\x03\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06confi\ +g\x03\0\x08\x04\0\x08generate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\ +\x0a\x04\0\x04poll\x01\x0b\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x04\ +\0\"golem:video/video-generation@1.0.0\x05\x0f\x01B\x10\x02\x03\x02\x01\x06\x04\0\ +\x0abase-video\x03\0\0\x02\x03\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\ +\x02\x01\x04\x04\0\x0bvideo-error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-i\ +nfo\x03\0\x06\x01j\x01s\x01\x05\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11\ +generate-lip-sync\x01\x09\x01ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08langua\ +ge\x0a\0\x0c\x04\0\x0blist-voices\x01\x0d\x04\0\x1agolem:video/lip-sync@1.0.0\x05\ +\x10\x01B\x1a\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\ +\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\ +\x02\x01\x02\x04\0\x11generation-config\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0bi\ +nput-image\x03\0\x08\x02\x03\x02\x01\x0c\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01\ +kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01@\x05\x08video-ids\x06prompt\x0c\x0fne\ +gative-prompt\x0c\x09cfg-scale\x0d\x10provider-options\x0f\0\x10\x04\0\x0cextend\ +-video\x01\x11\x01@\x01\x05input\x05\0\x10\x04\0\x0dupscale-video\x01\x12\x01@\x05\ +\x05input\x09\x06effect\x0b\x05model\x0c\x08duration\x0d\x04mode\x0c\0\x10\x04\0\ +\x16generate-video-effects\x01\x13\x01p\x09\x01@\x03\x0cinput-images\x14\x06prom\ +pt\x0c\x06config\x07\0\x10\x04\0\x16multi-image-generation\x01\x15\x04\0\x1agole\ +m:video/advanced@1.0.0\x05\x11\x04\0&golem:video-runway/video-library@1.0.0\x04\0\ +\x0b\x13\x01\0\x0dvideo-library\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0d\ +wit-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/video/runway/src/client.rs b/video/runway/src/client.rs new file mode 100644 index 000000000..e143487e6 --- /dev/null +++ b/video/runway/src/client.rs @@ -0,0 +1,360 @@ +use golem_video::error::{from_reqwest_error, video_error_from_status}; +use golem_video::exports::golem::video::types::VideoError; +use log::trace; +use reqwest::{Client, Method, Response}; +use serde::{Deserialize, Serialize}; + +const BASE_URL: &str = "https://api.dev.runwayml.com"; +const API_VERSION: &str = "2024-11-06"; + +/// The Runway API client for image-to-video generation +pub struct RunwayApi { + pub api_key: String, + pub client: Client, +} + +#[derive(Debug, Clone, Serialize)] +pub struct TextToImageRequest { + #[serde(rename = "promptText")] + pub prompt_text: String, + pub ratio: String, + pub model: String, // Must be "gen4_image" + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, + #[serde(rename = "contentModeration", skip_serializing_if = "Option::is_none")] + pub content_moderation: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenerationResponse { + pub id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextToImageResponse { + pub id: String, +} + +#[derive(Debug, Clone)] +pub enum PollResponse { + Processing, + Complete { + video_data: Vec, + mime_type: String, + }, +} + +#[derive(Debug, Clone)] +pub enum ImagePollResponse { + Processing, + Complete { image_url: String }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TaskResponse { + pub id: String, + pub status: String, + #[serde(rename = "createdAt")] + pub created_at: String, + pub output: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImageTaskResponse { + pub id: String, + pub status: String, + #[serde(rename = "createdAt")] + pub created_at: String, + pub output: Option>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct PromptImage { + pub uri: String, + pub position: String, // "first" or "last" +} + +#[derive(Debug, Clone, Serialize)] +pub struct ContentModeration { + #[serde(rename = "publicFigureThreshold")] + pub public_figure_threshold: String, // "auto" or "low" +} + +#[derive(Debug, Clone, Serialize)] +pub struct VideoUpscaleRequest { + #[serde(rename = "videoUri")] + pub video_uri: String, + pub model: String, // Must be "upscale_v1" +} + +#[derive(Debug, Clone, Serialize)] +pub struct ImageToVideoRequest { + #[serde(rename = "promptImage")] + pub prompt_image: Vec, + pub model: String, + pub ratio: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, + #[serde(rename = "promptText", skip_serializing_if = "Option::is_none")] + pub prompt_text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + #[serde(rename = "contentModeration", skip_serializing_if = "Option::is_none")] + pub content_moderation: Option, +} + +impl RunwayApi { + pub fn new(api_key: String) -> Self { + let client = Client::builder() + .default_headers(reqwest::header::HeaderMap::new()) + .build() + .expect("Failed to initialize HTTP client"); + Self { api_key, client } + } + + pub fn generate_video( + &self, + request: ImageToVideoRequest, + ) -> Result { + trace!("Sending image-to-video request to Runway API"); + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/image_to_video")) + .header("Authorization", format!("Bearer {}", &self.api_key)) + .header("X-Runway-Version", API_VERSION) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + pub fn poll_generation(&self, task_id: &str) -> Result { + trace!("Polling generation status for ID: {task_id}"); + + let response: Response = self + .client + .request(Method::GET, format!("{BASE_URL}/v1/tasks/{task_id}")) + .header("Authorization", format!("Bearer {}", &self.api_key)) + .header("X-Runway-Version", API_VERSION) + .send() + .map_err(|err| from_reqwest_error("Poll request failed", err))?; + + let status = response.status(); + + if status.is_success() { + let task_response: TaskResponse = response + .json() + .map_err(|err| from_reqwest_error("Failed to parse task response", err))?; + + match task_response.status.as_str() { + "PENDING" | "RUNNING" => Ok(PollResponse::Processing), + "SUCCEEDED" => { + if let Some(output) = task_response.output { + if let Some(video_url) = output.first() { + // Download the video from the URL + let video_data = self.download_video(video_url)?; + Ok(PollResponse::Complete { + video_data, + mime_type: "video/mp4".to_string(), + }) + } else { + Err(VideoError::InternalError( + "No output URL in successful task".to_string(), + )) + } + } else { + Err(VideoError::InternalError( + "No output in successful task".to_string(), + )) + } + } + "FAILED" | "CANCELED" => Err(VideoError::GenerationFailed( + "Task failed or was canceled".to_string(), + )), + _ => Err(VideoError::InternalError(format!( + "Unknown task status: {}", + task_response.status + ))), + } + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + Err(video_error_from_status(status, error_body)) + } + } + + pub fn cancel_task(&self, task_id: &str) -> Result<(), VideoError> { + trace!("Canceling task: {task_id}"); + + let response: Response = self + .client + .request(Method::DELETE, format!("{BASE_URL}/v1/tasks/{task_id}")) + .header("Authorization", format!("Bearer {}", &self.api_key)) + .header("X-Runway-Version", API_VERSION) + .send() + .map_err(|err| from_reqwest_error("Cancel request failed", err))?; + + if response.status().is_success() { + Ok(()) + } else { + let status = response.status(); + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + Err(video_error_from_status(status, error_body)) + } + } + + pub fn upscale_video( + &self, + request: VideoUpscaleRequest, + ) -> Result { + trace!("Sending video upscale request to Runway API"); + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/video_upscale")) + .header("Authorization", format!("Bearer {}", &self.api_key)) + .header("X-Runway-Version", API_VERSION) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Upscale request failed", err))?; + + parse_response(response) + } + + pub fn generate_text_to_image( + &self, + request: TextToImageRequest, + ) -> Result { + trace!("Sending text-to-image request to Runway API"); + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v1/text_to_image")) + .header("Authorization", format!("Bearer {}", &self.api_key)) + .header("X-Runway-Version", API_VERSION) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Text-to-image request failed", err))?; + + parse_text_to_image_response(response) + } + + pub fn poll_text_to_image(&self, task_id: &str) -> Result { + trace!("Polling text-to-image status for ID: {task_id}"); + + let response: Response = self + .client + .request(Method::GET, format!("{BASE_URL}/v1/tasks/{task_id}")) + .header("Authorization", format!("Bearer {}", &self.api_key)) + .header("X-Runway-Version", API_VERSION) + .send() + .map_err(|err| from_reqwest_error("Text-to-image poll request failed", err))?; + + let status = response.status(); + + if status.is_success() { + let task_response: ImageTaskResponse = response + .json() + .map_err(|err| from_reqwest_error("Failed to parse image task response", err))?; + + match task_response.status.as_str() { + "PENDING" | "RUNNING" => Ok(ImagePollResponse::Processing), + "SUCCEEDED" => { + if let Some(output) = task_response.output { + if let Some(image_url) = output.first() { + Ok(ImagePollResponse::Complete { + image_url: image_url.clone(), + }) + } else { + Err(VideoError::InternalError( + "No output URL in successful image task".to_string(), + )) + } + } else { + Err(VideoError::InternalError( + "No output in successful image task".to_string(), + )) + } + } + "FAILED" | "CANCELED" => Err(VideoError::GenerationFailed( + "Image generation task failed or was canceled".to_string(), + )), + _ => Err(VideoError::InternalError(format!( + "Unknown image task status: {}", + task_response.status + ))), + } + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + Err(video_error_from_status(status, error_body)) + } + } + + fn download_video(&self, url: &str) -> Result, VideoError> { + trace!("Downloading video from URL: {url}"); + + let response: Response = self + .client + .get(url) + .send() + .map_err(|err| from_reqwest_error("Failed to download video", err))?; + + if !response.status().is_success() { + return Err(VideoError::InternalError(format!( + "Failed to download video: HTTP {}", + response.status() + ))); + } + + let bytes = response + .bytes() + .map_err(|err| from_reqwest_error("Failed to read video data", err))?; + + Ok(bytes.to_vec()) + } +} + +fn parse_response(response: Response) -> Result { + let status = response.status(); + if status.is_success() { + response + .json::() + .map_err(|err| from_reqwest_error("Failed to decode response body", err)) + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to receive error response body", err))?; + + let error_message = format!("Request failed with {status}: {error_body}"); + Err(video_error_from_status(status, error_message)) + } +} + +fn parse_text_to_image_response(response: Response) -> Result { + let status = response.status(); + if status.is_success() { + response + .json::() + .map_err(|err| from_reqwest_error("Failed to decode text-to-image response body", err)) + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to receive error response body", err))?; + + let error_message = format!("Text-to-image request failed with {status}: {error_body}"); + Err(video_error_from_status(status, error_message)) + } +} diff --git a/video/runway/src/conversion.rs b/video/runway/src/conversion.rs new file mode 100644 index 000000000..507e73d8b --- /dev/null +++ b/video/runway/src/conversion.rs @@ -0,0 +1,478 @@ +use crate::client::{ + ContentModeration, ImagePollResponse, ImageToVideoRequest, PollResponse, PromptImage, + RunwayApi, TextToImageRequest, VideoUpscaleRequest, +}; +use golem_video::error::{invalid_input, unsupported_feature}; +use golem_video::exports::golem::video::types::{ + AspectRatio, GenerationConfig, ImageRole, JobStatus, MediaData, MediaInput, Resolution, Video, + VideoError, VideoResult, +}; +use std::collections::HashMap; + +pub fn media_input_to_request( + input: MediaInput, + config: GenerationConfig, +) -> Result { + match input { + MediaInput::Text(_) => Err(unsupported_feature( + "Text-to-video is not supported by Runway API", + )), + MediaInput::Video(_) => Err(unsupported_feature( + "Video-to-video is not supported by Runway API", + )), + MediaInput::Image(ref_image) => { + // Extract image data from new InputImage structure + let image_data = match ref_image.data.data { + MediaData::Url(url) => url, + MediaData::Bytes(raw_bytes) => { + // Convert bytes to data URI with proper mime type + use base64::Engine; + let base64_data = + base64::engine::general_purpose::STANDARD.encode(&raw_bytes.bytes); + let mime_type = if !raw_bytes.mime_type.is_empty() { + &raw_bytes.mime_type + } else { + "image/png" + }; + format!("data:{mime_type};base64,{base64_data}") + } + }; + + // Parse provider options + let options: HashMap = config + .provider_options + .map(|po| po.into_iter().map(|kv| (kv.key, kv.value)).collect()) + .unwrap_or_default(); + + // Determine model - default to gen3a_turbo + let model = config.model.unwrap_or_else(|| "gen3a_turbo".to_string()); + + // Validate model + if !matches!(model.as_str(), "gen3a_turbo" | "gen4_turbo") { + return Err(invalid_input("Model must be 'gen3a_turbo' or 'gen4_turbo'")); + } + + // Determine ratio based on aspect_ratio and resolution + let ratio = determine_ratio(&model, config.aspect_ratio, config.resolution)?; + + // Duration support + let duration = match config.duration_seconds { + Some(d) => { + let dur = d as u32; + if dur < 10 { + Some(5) // Default to 5 if less than 10 + } else { + Some(10) // Cap at 10 if 10 or above + } + } + None => Some(5), // Default to 5 if no duration provided + }; + + // Content moderation + let content_moderation = options.get("publicFigureThreshold").map(|threshold| { + let threshold_value = if threshold == "low" { "low" } else { "auto" }; + ContentModeration { + public_figure_threshold: threshold_value.to_string(), + } + }); + + // Create prompt images based on role and lastframe + let mut prompt_images = Vec::new(); + + // Determine position from image role (default to "first") + let position = match ref_image.role { + Some(ImageRole::First) => "first", + Some(ImageRole::Last) => "last", + None => "first", // Default to first frame + }; + + prompt_images.push(PromptImage { + uri: image_data, + position: position.to_string(), + }); + + // Handle lastframe if provided + if let Some(lastframe) = &config.lastframe { + let lastframe_data = match &lastframe.data { + MediaData::Url(url) => url.clone(), + MediaData::Bytes(raw_bytes) => { + use base64::Engine; + let base64_data = + base64::engine::general_purpose::STANDARD.encode(&raw_bytes.bytes); + let mime_type = if !raw_bytes.mime_type.is_empty() { + &raw_bytes.mime_type + } else { + "image/png" + }; + format!("data:{mime_type};base64,{base64_data}") + } + }; + + prompt_images.push(PromptImage { + uri: lastframe_data, + position: "last".to_string(), + }); + } + + // Use prompt text from the image if available + let prompt_text = ref_image.prompt; + + // Validate seed if provided + if let Some(seed_val) = config.seed { + if seed_val > 4294967295 { + return Err(invalid_input("Seed must be between 0 and 4294967295")); + } + } + + // Log warnings for unsupported built-in options + if config.negative_prompt.is_some() { + log::warn!("negative_prompt is not supported by Runway API and will be ignored"); + } + if config.scheduler.is_some() { + log::warn!("scheduler is not supported by Runway API and will be ignored"); + } + if config.guidance_scale.is_some() { + log::warn!("guidance_scale is not supported by Runway API and will be ignored"); + } + if config.enable_audio.is_some() { + log::warn!("enable_audio is not supported by Runway API and will be ignored"); + } + if config.enhance_prompt.is_some() { + log::warn!("enhance_prompt is not supported by Runway API and will be ignored"); + } + if config.static_mask.is_some() { + log::warn!("static_mask is not supported by Runway API and will be ignored"); + } + if config.dynamic_mask.is_some() { + log::warn!("dynamic_mask is not supported by Runway API and will be ignored"); + } + if config.camera_control.is_some() { + log::warn!("camera_control is not supported by Runway API and will be ignored"); + } + + Ok(ImageToVideoRequest { + prompt_image: prompt_images, + model, + ratio, + seed: config.seed, + prompt_text, + duration, + content_moderation, + }) + } + } +} + +fn determine_ratio( + model: &str, + aspect_ratio: Option, + _resolution: Option, +) -> Result { + // Default ratios by model + let default_ratio = match model { + "gen3a_turbo" => "1280:768", + "gen4_turbo" => "1280:720", + _ => return Err(invalid_input("Invalid model")), + }; + + // If no aspect ratio specified, use default + let target_aspect = aspect_ratio.unwrap_or(AspectRatio::Landscape); + + match model { + "gen3a_turbo" => match target_aspect { + AspectRatio::Landscape => Ok("1280:768".to_string()), + AspectRatio::Portrait => Ok("768:1280".to_string()), + AspectRatio::Square | AspectRatio::Cinema => { + log::warn!( + "Aspect ratio {target_aspect:?} not supported by gen3a_turbo, using landscape" + ); + Ok("1280:768".to_string()) + } + }, + "gen4_turbo" => match target_aspect { + AspectRatio::Landscape => Ok("1280:720".to_string()), + AspectRatio::Portrait => Ok("720:1280".to_string()), + AspectRatio::Square => Ok("960:960".to_string()), + AspectRatio::Cinema => Ok("1584:672".to_string()), + }, + _ => Ok(default_ratio.to_string()), + } +} + +pub fn generate_video( + client: &RunwayApi, + input: MediaInput, + config: GenerationConfig, +) -> Result { + match input { + MediaInput::Text(prompt) => { + // For text input, first generate an image, then use that for video generation + generate_text_to_video_via_image(client, prompt, config) + } + MediaInput::Image(_) => { + // For image input, use existing flow + let request = media_input_to_request(input, config)?; + let response = client.generate_video(request)?; + Ok(response.id) + } + MediaInput::Video(_) => Err(unsupported_feature( + "Video-to-video is not supported by Runway API", + )), + } +} + +fn generate_text_to_video_via_image( + client: &RunwayApi, + prompt: String, + config: GenerationConfig, +) -> Result { + // Step 1: Generate image from text + let image_task_id = generate_text_to_image(client, prompt.clone(), &config)?; + + // Step 2: Poll for image completion (with timeout) + let max_polls = 60; // 5 minutes with 5-second intervals + let mut polls = 0; + + let image_url = loop { + if polls >= max_polls { + return Err(VideoError::GenerationFailed( + "Text-to-image generation timed out".to_string(), + )); + } + + match poll_text_to_image_generation(client, &image_task_id)? { + Some(url) => break url, + None => { + // Sleep for 5 seconds before next poll + std::thread::sleep(std::time::Duration::from_secs(5)); + polls += 1; + } + } + }; + + // Step 3: Use the generated image URL for video generation + let image_input = MediaInput::Image(golem_video::exports::golem::video::types::Reference { + data: golem_video::exports::golem::video::types::InputImage { + data: MediaData::Url(image_url), + }, + prompt: Some(prompt), + role: Some(golem_video::exports::golem::video::types::ImageRole::First), + }); + + let request = media_input_to_request(image_input, config)?; + let response = client.generate_video(request)?; + Ok(response.id) +} + +pub fn poll_video_generation( + client: &RunwayApi, + task_id: String, +) -> Result { + match client.poll_generation(&task_id) { + Ok(PollResponse::Processing) => Ok(VideoResult { + status: JobStatus::Running, + videos: None, + metadata: None, + }), + Ok(PollResponse::Complete { + video_data, + mime_type, + }) => { + let video = Video { + uri: None, + base64_bytes: Some(video_data), + mime_type, + width: None, + height: None, + fps: None, + duration_seconds: None, + }; + + Ok(VideoResult { + status: JobStatus::Succeeded, + videos: Some(vec![video]), + metadata: None, + }) + } + Err(error) => Err(error), + } +} + +pub fn cancel_video_generation(client: &RunwayApi, task_id: String) -> Result { + client.cancel_task(&task_id)?; + Ok(format!("Task {task_id} canceled successfully")) +} + +// Text-to-Image functions for Runway +pub fn text_to_image_request( + prompt: String, + config: &GenerationConfig, +) -> Result { + // Parse provider options + let options: std::collections::HashMap = config + .provider_options + .as_ref() + .map(|po| { + po.iter() + .map(|kv| (kv.key.clone(), kv.value.clone())) + .collect() + }) + .unwrap_or_default(); + + // Determine ratio based on aspect_ratio and resolution + let ratio = determine_text_to_image_ratio(config.aspect_ratio, config.resolution)?; + + // Content moderation + let content_moderation = options.get("publicFigureThreshold").map(|threshold| { + let threshold_value = if threshold == "low" { "low" } else { "auto" }; + crate::client::ContentModeration { + public_figure_threshold: threshold_value.to_string(), + } + }); + + // Validate seed if provided + if let Some(seed_val) = config.seed { + if seed_val > 4294967295 { + return Err(invalid_input("Seed must be between 0 and 4294967295")); + } + } + + Ok(TextToImageRequest { + prompt_text: prompt, + ratio, + model: "gen4_image".to_string(), + seed: config.seed, + content_moderation, + }) +} + +fn determine_text_to_image_ratio( + aspect_ratio: Option, + _resolution: Option, +) -> Result { + let target_aspect = aspect_ratio.unwrap_or(AspectRatio::Landscape); + + match target_aspect { + AspectRatio::Landscape => Ok("1920:1080".to_string()), + AspectRatio::Portrait => Ok("1080:1920".to_string()), + AspectRatio::Square => Ok("1024:1024".to_string()), + AspectRatio::Cinema => Ok("1808:768".to_string()), + } +} + +pub fn generate_text_to_image( + client: &RunwayApi, + prompt: String, + config: &GenerationConfig, +) -> Result { + let request = text_to_image_request(prompt, config)?; + let response = client.generate_text_to_image(request)?; + Ok(response.id) +} + +pub fn poll_text_to_image_generation( + client: &RunwayApi, + task_id: &str, +) -> Result, VideoError> { + match client.poll_text_to_image(task_id) { + Ok(ImagePollResponse::Processing) => Ok(None), + Ok(ImagePollResponse::Complete { image_url }) => Ok(Some(image_url)), + Err(error) => Err(error), + } +} + +pub fn upscale_video( + client: &RunwayApi, + input: golem_video::exports::golem::video::types::BaseVideo, +) -> Result { + let video_uri = match input.data { + MediaData::Url(url) => Ok(url), + MediaData::Bytes(_) => Err(VideoError::UnsupportedFeature( + "Video effects generation is not supported by Runway API".to_string(), + )), + // Convert bytes to data URI for video with proper mime type + // Docs indicate they support bytes, but they aren't clear how + // so this goes to unsupported for now + // https://docs.dev.runwayml.com/api/#tag/Start-generating/paths/~1v1~1video_upscale/post + // https://docs.dev.runwayml.com/assets/inputs/#data-uris-base64-encoded-images + // below code results in 400 format error + /* + use base64::Engine; + let base64_data = base64::engine::general_purpose::STANDARD.encode(&raw_bytes.bytes); + let mime_type = if !raw_bytes.mime_type.is_empty() { + &raw_bytes.mime_type + } else { + "video/mp4" + }; + format!("data:{mime_type};base64,{base64_data}") + */ + }?; + + let request = VideoUpscaleRequest { + video_uri, + model: "upscale_v1".to_string(), + }; + + let response = client.upscale_video(request)?; + + // Return the task ID directly from Runway API + Ok(response.id) +} + +// Unsupported features + +pub fn generate_video_effects( + _client: &RunwayApi, + _input: golem_video::exports::golem::video::types::InputImage, + _effect: golem_video::exports::golem::video::types::EffectType, + _model: Option, + _duration: Option, + _mode: Option, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Video effects generation is not supported by Runway API".to_string(), + )) +} + +pub fn multi_image_generation( + _client: &RunwayApi, + _input_images: Vec, + _prompt: Option, + _config: GenerationConfig, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Multi-image generation is not supported by Runway API".to_string(), + )) +} + +pub fn generate_lip_sync_video( + _client: &RunwayApi, + _video: golem_video::exports::golem::video::types::BaseVideo, + _audio: golem_video::exports::golem::video::types::AudioSource, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Lip sync is not supported by Runway API".to_string(), + )) +} + +pub fn list_available_voices( + _client: &RunwayApi, + _language: Option, +) -> Result, VideoError> { + Err(VideoError::UnsupportedFeature( + "Voice listing is not supported by Runway API".to_string(), + )) +} + +pub fn extend_video( + _client: &RunwayApi, + _video_id: String, + _prompt: Option, + _negative_prompt: Option, + _cfg_scale: Option, + _provider_options: Option>, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Video extension is not supported by Runway API".to_string(), + )) +} diff --git a/video/runway/src/lib.rs b/video/runway/src/lib.rs new file mode 100644 index 000000000..495751913 --- /dev/null +++ b/video/runway/src/lib.rs @@ -0,0 +1,132 @@ +mod client; +mod conversion; + +use crate::client::RunwayApi; +use crate::conversion::{ + cancel_video_generation, extend_video, generate_lip_sync_video, generate_video, + generate_video_effects, list_available_voices, multi_image_generation, poll_video_generation, + upscale_video, +}; +use golem_video::config::with_config_key; +use golem_video::durability::{DurableVideo, ExtendedGuest}; +use golem_video::exports::golem::video::advanced::Guest as AdvancedGuest; +use golem_video::exports::golem::video::lip_sync::Guest as LipSyncGuest; +use golem_video::exports::golem::video::types::{ + AudioSource, BaseVideo, EffectType, GenerationConfig, InputImage, Kv, MediaInput, VideoError, + VideoResult, VoiceInfo, +}; +use golem_video::exports::golem::video::video_generation::Guest as VideoGenerationGuest; +use golem_video::LOGGING_STATE; + +struct RunwayComponent; + +impl RunwayComponent { + const ENV_VAR_NAME: &'static str = "RUNWAY_API_KEY"; +} + +impl VideoGenerationGuest for RunwayComponent { + fn generate(input: MediaInput, config: GenerationConfig) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + generate_video(&client, input, config) + }) + } + + fn poll(job_id: String) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + poll_video_generation(&client, job_id) + }) + } + + fn cancel(job_id: String) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + cancel_video_generation(&client, job_id) + }) + } +} + +impl LipSyncGuest for RunwayComponent { + fn generate_lip_sync(video: BaseVideo, audio: AudioSource) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + generate_lip_sync_video(&client, video, audio) + }) + } + + fn list_voices(language: Option) -> Result, VideoError> { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + list_available_voices(&client, language) + }) + } +} + +impl AdvancedGuest for RunwayComponent { + fn extend_video( + video_id: String, + prompt: Option, + negative_prompt: Option, + cfg_scale: Option, + provider_options: Option>, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + extend_video( + &client, + video_id, + prompt, + negative_prompt, + cfg_scale, + provider_options, + ) + }) + } + + fn upscale_video(input: BaseVideo) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + upscale_video(&client, input) + }) + } + + fn generate_video_effects( + input: InputImage, + effect: EffectType, + model: Option, + duration: Option, + mode: Option, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + generate_video_effects(&client, input, effect, model, duration, mode) + }) + } + + fn multi_image_generation( + input_images: Vec, + prompt: Option, + config: GenerationConfig, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = RunwayApi::new(api_key); + multi_image_generation(&client, input_images, prompt, config) + }) + } +} + +impl ExtendedGuest for RunwayComponent {} + +type DurableRunwayComponent = DurableVideo; + +golem_video::export_video!(DurableRunwayComponent with_types_in golem_video); diff --git a/video/runway/wit/deps/golem-video/golem-video.wit b/video/runway/wit/deps/golem-video/golem-video.wit new file mode 100644 index 000000000..8a2ecf138 --- /dev/null +++ b/video/runway/wit/deps/golem-video/golem-video.wit @@ -0,0 +1,256 @@ +package golem:video@1.0.0; + +interface types { + variant video-error { + invalid-input(string), + unsupported-feature(string), + quota-exceeded, + generation-failed(string), + cancelled, + internal-error(string), + } + + variant media-input { + text(string), + image(reference), + video(base-video), + } + + record reference { + data: input-image, + prompt: option, + role: option, + } + + enum image-role { + first, + last, + } + + record input-image { + data: media-data, + } + + record base-video { + data: media-data, + } + + record narration { + data: media-data, + } + + variant media-data { + url(string), + bytes(raw-bytes), + } + + record raw-bytes { + bytes: list, + mime-type: string, + } + + record static-mask { + mask: input-image, + } + + record dynamic-mask { + mask: input-image, + trajectories: list, + } + + record position { + x: u32, + y: u32, + } + + enum camera-movement { + simple, + down-back, + forward-up, + right-turn-forward, + left-turn-forward, + } + + record camera-config { + horizontal: f32, + vertical: f32, + pan: f32, + tilt: f32, + zoom: f32, + roll: f32, + } + + variant camera-control { + movement(camera-movement), + config(camera-config), + } + + record generation-config { + negative-prompt: option, + seed: option, + scheduler: option, + guidance-scale: option, + aspect-ratio: option, + duration-seconds: option, + resolution: option, + model: option, + enable-audio: option, + enhance-prompt: option, + provider-options: option>, + lastframe: option, + static-mask: option, + dynamic-mask: option, + camera-control: option, + } + + enum aspect-ratio { + square, + portrait, + landscape, + cinema, + } + + enum resolution { + sd, + hd, + fhd, + uhd, + } + + record kv { + key: string, + value: string, + } + + record video { + uri: option, + base64-bytes: option>, + mime-type: string, + width: option, + height: option, + fps: option, + duration-seconds: option, + } + + variant job-status { + pending, + running, + succeeded, + failed(string), + } + + record video-result { + status: job-status, + videos: option>, + metadata: option>, + } + + record text-to-speech { + text: string, + voice-id: string, + language: voice-language, + speed: u32, + } + + variant audio-source { + from-text(text-to-speech), + from-audio(narration), + } + + record voice-info { + voice-id: string, + name: string, + language: voice-language, + preview-url: option, + } + + enum voice-language { + en, + zh + } + + enum single-image-effects { + bloombloom, + dizzydizzy, + fuzzyfuzzy, + squish, + expansion, + } + + enum dual-image-effects { + hug, + kiss, + heart-gesture, + } + + record dual-effect { + effect: dual-image-effects, + second-image: input-image, + } + + variant effect-type { + single(single-image-effects), + dual(dual-effect), + } +} + +interface video-generation { + use types.{media-input, generation-config, video-result, video-error}; + + generate: func(input: media-input, config: generation-config) -> result; + poll: func(job-id: string) -> result; + cancel: func(job-id: string) -> result; +} + +interface lip-sync { + use types.{base-video, audio-source, video-error, voice-info}; + + generate-lip-sync: func( + video: base-video, + audio: audio-source, + ) -> result; + + list-voices: func(language: option) -> result, video-error>; +} + +interface advanced { + use types.{video-error, kv, base-video, generation-config, input-image, effect-type}; + + extend-video: func( + video-id: string, + prompt: option, + negative-prompt: option, + cfg-scale: option, + provider-options: option>, + ) -> result; + + upscale-video: func( + input: base-video, + ) -> result; + + generate-video-effects: func( + input: input-image, + effect: effect-type, + model: option, + duration: option, + mode: option, + ) -> result; + + multi-image-generation: func( + input-images: list, + prompt: option, + config: generation-config, + ) -> result; +} + +world video-library { + import types; + import video-generation; + import lip-sync; + import advanced; + + export video-generation; + export lip-sync; + export advanced; + export types; +} diff --git a/video/runway/wit/runway.wit b/video/runway/wit/runway.wit new file mode 100644 index 000000000..efc04c6e3 --- /dev/null +++ b/video/runway/wit/runway.wit @@ -0,0 +1,5 @@ +package golem:video-runway@1.0.0; + +world video-library { + include golem:video/video-library@1.0.0; +} \ No newline at end of file diff --git a/video/stability/Cargo.toml b/video/stability/Cargo.toml new file mode 100644 index 000000000..eb1ae8313 --- /dev/null +++ b/video/stability/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "golem-video-stability" +version = "0.0.0" +edition = "2021" +license = "Apache-2.0" +homepage = "https://golem.cloud" +repository = "https://github.com/golemcloud/golem-llm" +description = "WebAssembly component for working with Stability AI video APIs, with special support for Golem Cloud" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["durability"] +durability = ["golem-rust/durability", "golem-video/durability"] + +[dependencies] +golem-video = { path = "../video", version = "0.0.0", default-features = false } + +golem-rust = { workspace = true } +log = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +wit-bindgen-rt = { workspace = true } +base64 = { workspace = true } +image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } + +[package.metadata.component] +package = "golem:video-stability" + +[package.metadata.component.bindings] +generate_unused_types = true + +[package.metadata.component.bindings.with] +"golem:video/types@1.0.0" = "golem_video::golem::video::types" +"golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +"golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +"golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" + +[package.metadata.component.target] +path = "wit" + +[package.metadata.component.target.dependencies] +"golem:video" = { path = "wit/deps/golem-video" } \ No newline at end of file diff --git a/video/stability/src/bindings.rs b/video/stability/src/bindings.rs new file mode 100644 index 000000000..6fd1e79f7 --- /dev/null +++ b/video/stability/src/bindings.rs @@ -0,0 +1,148 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +// * runtime_path: "wit_bindgen_rt" +// * with "golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +// * with "golem:video/types@1.0.0" = "golem_video::golem::video::types" +// * with "golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +// * with "golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" +// * generate_unused_types +use golem_video::golem::video::types as __with_name0; +use golem_video::golem::video::video_generation as __with_name1; +use golem_video::golem::video::lip_sync as __with_name2; +use golem_video::golem::video::advanced as __with_name3; +use golem_video::golem::video::types as __with_name4; +use golem_video::golem::video::video_generation as __with_name5; +use golem_video::golem::video::lip_sync as __with_name6; +use golem_video::golem::video::advanced as __with_name7; +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:video-stability@1.0.0:video-library:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 5485] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe9)\x01A\x02\x01A\x1a\ +\x01BO\x01q\x06\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-e\ +xceeded\0\0\x11generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\ +\x04\0\x0bvideo-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\ +\x02\x01p}\x01r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\ +\x02\x03url\x01s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04\ +data\x08\x04\0\x0binput-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06p\ +rompt\x0b\x04role\x0c\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0a\ +base-video\x03\0\x0f\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\ +\0\x04\0\x0bmedia-input\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\ +\x01r\x01\x04mask\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08\ +position\x03\0\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdy\ +namic-mask\x03\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn\ +-forward\x11left-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahor\ +izontalv\x08verticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\ +\0\x1e\x01q\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-contro\ +l\x03\0\x20\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspec\ +t-ratio\x03\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01\ +r\x02\x03keys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p\ +'\x01k-\x01k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed\ +(\x09scheduler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0ar\ +esolution+\x05model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\ +\x09lastframe/\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11gene\ +ration-config\x03\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime\ +-types\x05width6\x06height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01\ +q\x04\x07pending\0\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0aj\ +ob-status\x03\09\x01p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0c\ +video-result\x03\0=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\ +\x04texts\x08voice-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0\ +A\x01q\x02\x09from-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-sourc\ +e\x03\0C\x01r\x04\x08voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\ +\0\x0avoice-info\x03\0E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06sq\ +uish\x09expansion\x04\0\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0d\ +heart-gesture\x04\0\x12dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csec\ +ond-image\x0a\x04\0\x0bdual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\ +\x01\xcc\0\0\x04\0\x0beffect-type\x03\0M\x03\0\x17golem:video/types@1.0.0\x05\0\x02\ +\x03\0\0\x0bmedia-input\x02\x03\0\0\x11generation-config\x02\x03\0\0\x0cvideo-re\ +sult\x02\x03\0\0\x0bvideo-error\x01B\x10\x02\x03\x02\x01\x01\x04\0\x0bmedia-inpu\ +t\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\x03\0\x02\x02\x03\x02\x01\ +\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\ +\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06config\x03\0\x08\x04\0\x08gene\ +rate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\x0a\x04\0\x04poll\x01\x0b\ +\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x03\0\"golem:video/video-gene\ +ration@1.0.0\x05\x05\x02\x03\0\0\x0abase-video\x02\x03\0\0\x0caudio-source\x02\x03\ +\0\0\x0avoice-info\x01B\x10\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\0\x02\x03\ +\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\x02\x01\x04\x04\0\x0bvideo-\ +error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-info\x03\0\x06\x01j\x01s\x01\x05\ +\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11generate-lip-sync\x01\x09\x01\ +ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08language\x0a\0\x0c\x04\0\x0blist-vo\ +ices\x01\x0d\x03\0\x1agolem:video/lip-sync@1.0.0\x05\x09\x02\x03\0\0\x02kv\x02\x03\ +\0\0\x0binput-image\x02\x03\0\0\x0beffect-type\x01B\x1a\x02\x03\x02\x01\x04\x04\0\ +\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\ +\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\x02\x01\x02\x04\0\x11generation-confi\ +g\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0binput-image\x03\0\x08\x02\x03\x02\x01\x0c\ +\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01\ +@\x05\x08video-ids\x06prompt\x0c\x0fnegative-prompt\x0c\x09cfg-scale\x0d\x10prov\ +ider-options\x0f\0\x10\x04\0\x0cextend-video\x01\x11\x01@\x01\x05input\x05\0\x10\ +\x04\0\x0dupscale-video\x01\x12\x01@\x05\x05input\x09\x06effect\x0b\x05model\x0c\ +\x08duration\x0d\x04mode\x0c\0\x10\x04\0\x16generate-video-effects\x01\x13\x01p\x09\ +\x01@\x03\x0cinput-images\x14\x06prompt\x0c\x06config\x07\0\x10\x04\0\x16multi-i\ +mage-generation\x01\x15\x03\0\x1agolem:video/advanced@1.0.0\x05\x0d\x01BO\x01q\x06\ +\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-exceeded\0\0\x11\ +generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\x04\0\x0bvide\ +o-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\x02\x01p}\x01\ +r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\x02\x03url\x01\ +s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04data\x08\x04\0\x0b\ +input-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06prompt\x0b\x04role\x0c\ +\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0abase-video\x03\0\x0f\ +\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\0\x04\0\x0bmedia-in\ +put\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\x01r\x01\x04mask\ +\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08position\x03\0\ +\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdynamic-mask\x03\ +\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn-forward\x11le\ +ft-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahorizontalv\x08ve\ +rticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\0\x1e\x01q\ +\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-control\x03\0\x20\ +\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspect-ratio\x03\ +\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01r\x02\x03k\ +eys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p'\x01k-\x01\ +k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed(\x09schedu\ +ler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0aresolution+\x05\ +model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\x09lastframe/\ +\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11generation-config\x03\ +\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime-types\x05width6\x06\ +height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01q\x04\x07pending\0\ +\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0ajob-status\x03\09\x01\ +p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0cvideo-result\x03\0\ +=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\x04texts\x08voice\ +-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0A\x01q\x02\x09from\ +-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-source\x03\0C\x01r\x04\x08\ +voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\0\x0avoice-info\x03\0\ +E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06squish\x09expansion\x04\0\ +\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0dheart-gesture\x04\0\x12\ +dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csecond-image\x0a\x04\0\x0b\ +dual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\x01\xcc\0\0\x04\0\x0be\ +ffect-type\x03\0M\x04\0\x17golem:video/types@1.0.0\x05\x0e\x01B\x10\x02\x03\x02\x01\ +\x01\x04\0\x0bmedia-input\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\ +\x03\0\x02\x02\x03\x02\x01\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\ +\x04\0\x0bvideo-error\x03\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06confi\ +g\x03\0\x08\x04\0\x08generate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\ +\x0a\x04\0\x04poll\x01\x0b\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x04\ +\0\"golem:video/video-generation@1.0.0\x05\x0f\x01B\x10\x02\x03\x02\x01\x06\x04\0\ +\x0abase-video\x03\0\0\x02\x03\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\ +\x02\x01\x04\x04\0\x0bvideo-error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-i\ +nfo\x03\0\x06\x01j\x01s\x01\x05\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11\ +generate-lip-sync\x01\x09\x01ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08langua\ +ge\x0a\0\x0c\x04\0\x0blist-voices\x01\x0d\x04\0\x1agolem:video/lip-sync@1.0.0\x05\ +\x10\x01B\x1a\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\ +\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\ +\x02\x01\x02\x04\0\x11generation-config\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0bi\ +nput-image\x03\0\x08\x02\x03\x02\x01\x0c\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01\ +kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01@\x05\x08video-ids\x06prompt\x0c\x0fne\ +gative-prompt\x0c\x09cfg-scale\x0d\x10provider-options\x0f\0\x10\x04\0\x0cextend\ +-video\x01\x11\x01@\x01\x05input\x05\0\x10\x04\0\x0dupscale-video\x01\x12\x01@\x05\ +\x05input\x09\x06effect\x0b\x05model\x0c\x08duration\x0d\x04mode\x0c\0\x10\x04\0\ +\x16generate-video-effects\x01\x13\x01p\x09\x01@\x03\x0cinput-images\x14\x06prom\ +pt\x0c\x06config\x07\0\x10\x04\0\x16multi-image-generation\x01\x15\x04\0\x1agole\ +m:video/advanced@1.0.0\x05\x11\x04\0)golem:video-stability/video-library@1.0.0\x04\ +\0\x0b\x13\x01\0\x0dvideo-library\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\ +\x0dwit-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/video/stability/src/client.rs b/video/stability/src/client.rs new file mode 100644 index 000000000..3026b9d21 --- /dev/null +++ b/video/stability/src/client.rs @@ -0,0 +1,375 @@ +use golem_video::error::{from_reqwest_error, video_error_from_status}; +use golem_video::exports::golem::video::types::VideoError; +use log::trace; +use reqwest::{Client, Method, Response}; +use serde::{Deserialize, Serialize}; + +const BASE_URL: &str = "https://api.stability.ai"; +const ACCEPT_HEADER_VIDEO: &str = "video/*"; +const ACCEPT_HEADER_IMAGE: &str = "image/*"; + +#[derive(Debug, Clone)] +pub struct ImageToVideoRequest { + pub image_data: Vec, + pub seed: Option, + pub cfg_scale: Option, + pub motion_bucket_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenerationResponse { + pub id: String, +} + +#[derive(Debug, Clone)] +pub enum PollResponse { + Processing, + Complete { + video_data: Vec, + mime_type: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ErrorResponse { + pub error: ErrorDetails, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ErrorDetails { + pub message: String, + #[serde(rename = "type")] + pub error_type: String, +} + +#[derive(Debug, Clone)] +pub struct TextToImageRequest { + pub prompt: String, + pub aspect_ratio: Option, + pub negative_prompt: Option, + pub seed: Option, + pub style_preset: Option, + pub output_format: String, +} + +#[derive(Debug, Clone)] +pub struct TextToImageResponse { + pub image_data: Vec, + pub seed: Option, + pub finish_reason: Option, +} + +/// The Stability API client for image-to-video generation +/// The Accept header in reqwest can only be set in initial client creation +/// Trying to set it during call causes it to add the new header and */*, +/// Two clients, one for polling with video/* and one for image/* (Part of text->image->video), +/// Issue:https://github.com/seanmonstar/reqwest/issues/2279 +pub struct StabilityApi { + api_key: String, + client: Client, + client_image: Client, +} + +impl StabilityApi { + pub fn new(api_key: String) -> Self { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + "accept", + ACCEPT_HEADER_VIDEO.parse().expect("Invalid header value"), + ); + + let mut headers_image = reqwest::header::HeaderMap::new(); + headers_image.insert( + "accept", + ACCEPT_HEADER_IMAGE.parse().expect("Invalid header value"), + ); + + let client = Client::builder() + .default_headers(headers) + .build() + .expect("Failed to initialize HTTP client"); + + let client_image = Client::builder() + .default_headers(headers_image) + .build() + .expect("Failed to initialize HTTP client"); + + Self { + api_key, + client, + client_image, + } + } + + // Stability API only supports image-to-video generation + // We use their text-to-image API to generate the image + // and then use the image-to-video API to generate the video + + // Generate video from image + pub fn generate_video( + &self, + request: ImageToVideoRequest, + ) -> Result { + trace!("Sending image-to-video request to Stability API"); + + // Manually construct multipart/form-data body + // multipart is not supported golem reqwest, rand WASM conflict + // so we create it manually + let boundary = generate_boundary(); + let body = build_multipart_body(&request, &boundary); + + let response: Response = self + .client + .request(Method::POST, format!("{BASE_URL}/v2beta/image-to-video")) + .header("authorization", format!("Bearer {}", &self.api_key)) + .header( + "content-type", + format!("multipart/form-data; boundary={boundary}"), + ) + .body(body) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + // Poll for video generation status + pub fn poll_generation(&self, generation_id: &str) -> Result { + trace!("Polling generation status for ID: {generation_id}"); + + let response: Response = self + .client + .request( + Method::GET, + format!("{BASE_URL}/v2beta/image-to-video/result/{generation_id}"), + ) + .header("authorization", format!("Bearer {}", &self.api_key)) + .send() + .map_err(|err| from_reqwest_error("Poll request failed", err))?; + + let status = response.status(); + + if status == reqwest::StatusCode::ACCEPTED { + // 202 - Still processing + Ok(PollResponse::Processing) + } else if status.is_success() { + // 200 - Complete, get video data + let video_bytes = response + .bytes() + .map_err(|err| from_reqwest_error("Failed to read video data", err))?; + + Ok(PollResponse::Complete { + video_data: video_bytes.to_vec(), + mime_type: "video/mp4".to_string(), + }) + } else { + // Error response + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + // Try to parse as JSON error, otherwise use raw text + let error_message = + if let Ok(error_response) = serde_json::from_str::(&error_body) { + error_response.error.message + } else { + error_body + }; + + Err(video_error_from_status(status, error_message)) + } + } + + // Generate image from text as part of text->image->video + pub fn generate_text_to_image( + &self, + request: TextToImageRequest, + ) -> Result { + trace!("Sending text-to-image request to Stability API"); + + // Manually construct multipart/form-data body + let boundary = generate_boundary(); + let body = build_text_to_image_multipart_body(&request, &boundary); + + let response: Response = self + .client_image + .request( + Method::POST, + format!("{BASE_URL}/v2beta/stable-image/generate/core"), + ) + .header("authorization", format!("Bearer {}", &self.api_key)) + .header( + "content-type", + format!("multipart/form-data; boundary={boundary}"), + ) + .body(body) + .send() + .map_err(|err| from_reqwest_error("Text-to-image request failed", err))?; + + let status = response.status(); + + if status.is_success() { + let seed = response + .headers() + .get("seed") + .and_then(|h| h.to_str().ok()) + .map(|s| s.to_string()); + + let finish_reason = response + .headers() + .get("finish-reason") + .and_then(|h| h.to_str().ok()) + .map(|s| s.to_string()); + + let image_data = response + .bytes() + .map_err(|err| from_reqwest_error("Failed to read image data", err))?; + + Ok(TextToImageResponse { + image_data: image_data.to_vec(), + seed, + finish_reason, + }) + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + let error_message = + if let Ok(error_response) = serde_json::from_str::(&error_body) { + error_response.error.message + } else { + error_body + }; + + Err(video_error_from_status(status, error_message)) + } + } +} + +// Helper functions +fn generate_boundary() -> String { + // Generate a simple boundary using a timestamp-based approach + // Since we can't use rand in WASM, we'll use a deterministic approach + format!( + "----formdata-golem-{}", + std::env::var("GOLEM_WORKER_NAME").unwrap_or_else(|_| "stability".to_string()) + ) +} + +fn build_multipart_body(request: &ImageToVideoRequest, boundary: &str) -> Vec { + let mut body = Vec::new(); + + // Add image field + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice( + b"Content-Disposition: form-data; name=\"image\"; filename=\"image.png\"\r\n", + ); + body.extend_from_slice(b"Content-Type: image/png\r\n\r\n"); + body.extend_from_slice(&request.image_data); + body.extend_from_slice(b"\r\n"); + + // Add optional fields + if let Some(seed) = request.seed { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"seed\"\r\n\r\n"); + body.extend_from_slice(seed.to_string().as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + if let Some(cfg_scale) = request.cfg_scale { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"cfg_scale\"\r\n\r\n"); + body.extend_from_slice(cfg_scale.to_string().as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + if let Some(motion_bucket_id) = request.motion_bucket_id { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice( + b"Content-Disposition: form-data; name=\"motion_bucket_id\"\r\n\r\n", + ); + body.extend_from_slice(motion_bucket_id.to_string().as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + // Close boundary + body.extend_from_slice(format!("--{boundary}--\r\n").as_bytes()); + + body +} + +fn build_text_to_image_multipart_body(request: &TextToImageRequest, boundary: &str) -> Vec { + let mut body = Vec::new(); + + // Add prompt field + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"prompt\"\r\n\r\n"); + body.extend_from_slice(request.prompt.as_bytes()); + body.extend_from_slice(b"\r\n"); + + // Add aspect_ratio if provided + if let Some(aspect_ratio) = &request.aspect_ratio { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n"); + body.extend_from_slice(aspect_ratio.as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + // Add negative_prompt if provided + if let Some(negative_prompt) = &request.negative_prompt { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"negative_prompt\"\r\n\r\n"); + body.extend_from_slice(negative_prompt.as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + // Add seed if provided + if let Some(seed) = request.seed { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"seed\"\r\n\r\n"); + body.extend_from_slice(seed.to_string().as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + // Add style_preset if provided + if let Some(style_preset) = &request.style_preset { + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"style_preset\"\r\n\r\n"); + body.extend_from_slice(style_preset.as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + // Add output_format + body.extend_from_slice(format!("--{boundary}\r\n").as_bytes()); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"output_format\"\r\n\r\n"); + body.extend_from_slice(request.output_format.as_bytes()); + body.extend_from_slice(b"\r\n"); + + // Close boundary + body.extend_from_slice(format!("--{boundary}--\r\n").as_bytes()); + + body +} + +fn parse_response(response: Response) -> Result { + let status = response.status(); + if status.is_success() { + response + .json::() + .map_err(|err| from_reqwest_error("Failed to decode response body", err)) + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to receive error response body", err))?; + + let error_message = + if let Ok(error_response) = serde_json::from_str::(&error_body) { + error_response.error.message + } else { + format!("Request failed with {status}: {error_body}") + }; + + Err(video_error_from_status(status, error_message)) + } +} diff --git a/video/stability/src/conversion.rs b/video/stability/src/conversion.rs new file mode 100644 index 000000000..d3bd7f764 --- /dev/null +++ b/video/stability/src/conversion.rs @@ -0,0 +1,464 @@ +use crate::client::{ImageToVideoRequest, PollResponse, StabilityApi, TextToImageRequest}; +use golem_video::error::{internal_error, invalid_input, unsupported_feature}; +use golem_video::exports::golem::video::types::{ + AspectRatio, GenerationConfig, JobStatus, MediaData, MediaInput, Video, VideoError, VideoResult, +}; +use golem_video::utils::download_image_from_url; +use image::ImageFormat; +use std::collections::HashMap; +use std::io::Cursor; + +/// Stability API supported dimensions +/// Stability only accepts these resolutions +/// We resize the image to the resolution before sending it to the API +/// Other providers do this at their end +#[derive(Debug, Clone, Copy)] +struct StabilityDimensions { + width: u32, + height: u32, +} + +impl StabilityDimensions { + const LANDSCAPE: Self = Self { + width: 1024, + height: 576, + }; // 16:9 + const PORTRAIT: Self = Self { + width: 576, + height: 1024, + }; // 9:16 + const SQUARE: Self = Self { + width: 768, + height: 768, + }; // 1:1 +} + +/// Helper function to determine target dimensions based on aspect ratio configuration +fn determine_target_dimensions(aspect_ratio: Option) -> StabilityDimensions { + match aspect_ratio { + Some(AspectRatio::Square) => StabilityDimensions::SQUARE, + Some(AspectRatio::Portrait) => StabilityDimensions::PORTRAIT, + Some(AspectRatio::Landscape) | Some(AspectRatio::Cinema) | None => { + // Default to landscape, cinema maps to 16:9 + if matches!(aspect_ratio, Some(AspectRatio::Cinema)) { + log::warn!("Cinema aspect ratio mapped to 16:9 landscape for Stability API"); + } + StabilityDimensions::LANDSCAPE + } + } +} + +/// Helper function to process image data to meet Stability's dimension requirements +fn process_image_for_stability( + image_data: &[u8], + target_dims: StabilityDimensions, +) -> Result, VideoError> { + // Load image from bytes + let img = image::load_from_memory(image_data) + .map_err(|e| invalid_input(format!("Failed to decode image: {e}")))?; + + log::debug!( + "Original image dimensions: {}x{}", + img.width(), + img.height() + ); + log::debug!( + "Target dimensions: {}x{}", + target_dims.width, + target_dims.height + ); + + // Calculate target aspect ratio + let target_aspect = target_dims.width as f32 / target_dims.height as f32; + let current_aspect = img.width() as f32 / img.height() as f32; + + // Determine crop dimensions to match target aspect ratio + let (crop_width, crop_height) = if current_aspect > target_aspect { + // Image is wider than target, crop width + let new_width = (img.height() as f32 * target_aspect) as u32; + (new_width, img.height()) + } else { + // Image is taller than target, crop height + let new_height = (img.width() as f32 / target_aspect) as u32; + (img.width(), new_height) + }; + + // Calculate crop position for center crop + let crop_x = (img.width().saturating_sub(crop_width)) / 2; + let crop_y = (img.height().saturating_sub(crop_height)) / 2; + + log::debug!("Cropping to {crop_width}x{crop_height} at ({crop_x}, {crop_y})"); + + // Perform center crop + let cropped = img.crop_imm(crop_x, crop_y, crop_width, crop_height); + + // Resize to target dimensions + let resized = cropped.resize_exact( + target_dims.width, + target_dims.height, + image::imageops::FilterType::Lanczos3, + ); + + log::debug!( + "Final processed dimensions: {}x{}", + resized.width(), + resized.height() + ); + + // Convert back to bytes (PNG format) + let mut output = Vec::new(); + let mut cursor = Cursor::new(&mut output); + + resized + .write_to(&mut cursor, ImageFormat::Png) + .map_err(|e| internal_error(format!("Failed to encode processed image: {e}")))?; + + Ok(output) +} + +/// Helper function to map WIT aspect ratio to Stability text-to-image API format +fn map_aspect_ratio_to_stability_t2i(aspect_ratio: Option) -> Option { + match aspect_ratio { + Some(AspectRatio::Square) => Some("1:1".to_string()), + Some(AspectRatio::Portrait) => Some("9:16".to_string()), + Some(AspectRatio::Landscape) => Some("16:9".to_string()), + Some(AspectRatio::Cinema) => Some("21:9".to_string()), + None => None, // Let API use default + } +} + +/// Generate image from text using Stability's text-to-image API +fn generate_image_from_text( + client: &StabilityApi, + prompt: String, + config: &GenerationConfig, +) -> Result, VideoError> { + log::debug!("Generating image from text: {prompt}"); + + // Parse provider options + let options: HashMap = config + .provider_options + .as_ref() + .map(|po| { + po.iter() + .map(|kv| (kv.key.clone(), kv.value.clone())) + .collect() + }) + .unwrap_or_default(); + + // Get style preset from provider options + let style_preset = options.get("style_preset").cloned(); + + // Validate seed range for text-to-image (same as video generation) + if let Some(seed_val) = config.seed { + if seed_val > 4294967294 { + return Err(invalid_input("Seed must be between 0 and 4294967294")); + } + } + + let t2i_request = TextToImageRequest { + prompt, + aspect_ratio: map_aspect_ratio_to_stability_t2i(config.aspect_ratio), + negative_prompt: config.negative_prompt.clone(), + seed: config.seed, + style_preset, + output_format: "png".to_string(), + }; + + match client.generate_text_to_image(t2i_request) { + Ok(response) => { + log::debug!("Successfully generated image from text"); + if let Some(seed) = response.seed { + log::debug!("Text-to-image used seed: {seed}"); + } + if let Some(finish_reason) = response.finish_reason { + log::debug!("Text-to-image finish reason: {finish_reason}"); + } + Ok(response.image_data) + } + Err(err) => { + log::error!("Failed to generate image from text: {err:?}"); + Err(internal_error(format!( + "Text-to-image generation failed: {err}" + ))) + } + } +} + +// Make request for video generation +pub fn media_input_to_request( + input: MediaInput, + config: GenerationConfig, +) -> Result { + match input { + MediaInput::Text(_) => Err(internal_error( + "Text processing should be handled in generate_video function", + )), + MediaInput::Video(_) => Err(unsupported_feature( + "Video processing error should be handled in generate_video function", + )), + MediaInput::Image(ref_image) => { + // Determine target dimensions based on aspect ratio config + let target_dims = determine_target_dimensions(config.aspect_ratio); + + // Extract and process image data + let processed_image_data = match ref_image.data.data { + MediaData::Url(url) => { + // Download the image from the URL and process it + let raw_bytes = download_image_from_url(&url)?; + process_image_for_stability(&raw_bytes.bytes, target_dims)? + } + MediaData::Bytes(raw_bytes) => { + // Process the image bytes directly + process_image_for_stability(&raw_bytes.bytes, target_dims)? + } + }; + + // Note: Stability doesn't support prompts with images and image roles, so we ignore prompt and role + if ref_image.role.is_some() { + log::warn!("image role positioning (first/last) is not supported by Stability API and will be ignored"); + } + + // Parse provider options - only for parameters not directly supported in WIT + let options: HashMap = config + .provider_options + .map(|po| po.into_iter().map(|kv| (kv.key, kv.value)).collect()) + .unwrap_or_default(); + + // Use built-in config fields directly + let seed = config.seed; + let cfg_scale = config.guidance_scale; + + // motion_bucket_id is only available via provider options since it's Stability-specific + let motion_bucket_id = options + .get("motion_bucket_id") + .and_then(|s| s.parse::().ok()); + + // Validate parameter ranges according to Stability API + if let Some(seed_val) = seed { + if seed_val > 4294967294 { + return Err(invalid_input("Seed must be between 0 and 4294967294")); + } + } + + if let Some(cfg_val) = cfg_scale { + if !(0.0..=10.0).contains(&cfg_val) { + return Err(invalid_input( + "CFG scale (guidance_scale) must be between 0.0 and 10.0", + )); + } + } + + if let Some(bucket_val) = motion_bucket_id { + if !(1..=255).contains(&bucket_val) { + return Err(invalid_input("Motion bucket ID must be between 1 and 255")); + } + } + + // Log warnings for unsupported built-in options + if config.model.is_some() { + log::warn!("model is not supported by Stability API and will be ignored"); + } + if config.negative_prompt.is_some() { + log::warn!("negative_prompt is not supported by Stability API and will be ignored"); + } + if config.scheduler.is_some() { + log::warn!("scheduler is not supported by Stability API and will be ignored"); + } + if config.aspect_ratio.is_some() { + log::info!( + "aspect_ratio processed and mapped to Stability dimensions: {}x{}", + target_dims.width, + target_dims.height + ); + } + if config.duration_seconds.is_some() { + log::warn!( + "duration_seconds is not supported by Stability API and will be ignored" + ); + } + if config.resolution.is_some() { + log::warn!("resolution is handled by aspect ratio mapping to Stability dimensions"); + } + if config.enable_audio.is_some() { + log::warn!("enable_audio is not supported by Stability API and will be ignored"); + } + if config.enhance_prompt.is_some() { + log::warn!("enhance_prompt is not supported by Stability API and will be ignored"); + } + if config.lastframe.is_some() { + log::warn!("lastframe is not supported by Stability API and will be ignored"); + } + if config.static_mask.is_some() { + log::warn!("static_mask is not supported by Stability API and will be ignored"); + } + if config.dynamic_mask.is_some() { + log::warn!("dynamic_mask is not supported by Stability API and will be ignored"); + } + if config.camera_control.is_some() { + log::warn!("camera_control is not supported by Stability API and will be ignored"); + } + + Ok(ImageToVideoRequest { + image_data: processed_image_data, + seed, + cfg_scale, + motion_bucket_id, + }) + } + } +} + +// Generate video from text or image, text->image->video, from video is unsupported +pub fn generate_video( + client: &StabilityApi, + input: MediaInput, + config: GenerationConfig, +) -> Result { + match input { + MediaInput::Text(prompt) => { + log::info!("Processing text-to-video request via text-to-image + image-to-video"); + + // First generate image from text + let image_data = generate_image_from_text(client, prompt, &config)?; + + // Create a new MediaInput with the generated image + let image_input = + MediaInput::Image(golem_video::exports::golem::video::types::Reference { + data: golem_video::exports::golem::video::types::InputImage { + data: MediaData::Bytes( + golem_video::exports::golem::video::types::RawBytes { + bytes: image_data, + mime_type: "image/png".to_string(), + }, + ), + }, + prompt: None, + role: None, + }); + + // Now generate video from the image + let request = media_input_to_request(image_input, config)?; + let response = client.generate_video(request)?; + + log::info!("Successfully initiated text-to-video generation"); + Ok(response.id) + } + MediaInput::Image(_) => { + let request = media_input_to_request(input, config)?; + let response = client.generate_video(request)?; + Ok(response.id) + } + MediaInput::Video(_) => Err(unsupported_feature( + "Video-to-video is not supported by Stability API", + )), + } +} + +// Poll for video generation status +pub fn poll_video_generation( + client: &StabilityApi, + task_id: String, +) -> Result { + match client.poll_generation(&task_id) { + Ok(PollResponse::Processing) => Ok(VideoResult { + status: JobStatus::Running, + videos: None, + metadata: None, + }), + Ok(PollResponse::Complete { + video_data, + mime_type, + }) => { + let video = Video { + uri: None, + base64_bytes: Some(video_data), + mime_type, + width: None, + height: None, + fps: None, + duration_seconds: None, + }; + + Ok(VideoResult { + status: JobStatus::Succeeded, + videos: Some(vec![video]), + metadata: None, + }) + } + Err(error) => Err(error), + } +} + +// Unsupported features + +pub fn cancel_video_generation(_task_id: String) -> Result { + Err(unsupported_feature( + "Video generation cancellation is not supported by Stability API", + )) +} + +pub fn generate_lip_sync_video( + _client: &StabilityApi, + _video: golem_video::exports::golem::video::types::BaseVideo, + _audio: golem_video::exports::golem::video::types::AudioSource, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Lip sync is not supported by Stability API".to_string(), + )) +} + +pub fn list_available_voices( + _client: &StabilityApi, + _language: Option, +) -> Result, VideoError> { + Err(VideoError::UnsupportedFeature( + "Voice listing is not supported by Stability API".to_string(), + )) +} + +pub fn extend_video( + _client: &StabilityApi, + _video_id: String, + _prompt: Option, + _negative_prompt: Option, + _cfg_scale: Option, + _provider_options: Option>, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Video extension is not supported by Stability API".to_string(), + )) +} + +pub fn upscale_video( + _client: &StabilityApi, + _input: golem_video::exports::golem::video::types::BaseVideo, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Video upscaling is not supported by Stability API".to_string(), + )) +} + +pub fn generate_video_effects( + _client: &StabilityApi, + _input: golem_video::exports::golem::video::types::InputImage, + _effect: golem_video::exports::golem::video::types::EffectType, + _model: Option, + _duration: Option, + _mode: Option, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Video effects generation is not supported by Stability API".to_string(), + )) +} + +pub fn multi_image_generation( + _client: &StabilityApi, + _input_images: Vec, + _prompt: Option, + _config: GenerationConfig, +) -> Result { + Err(VideoError::UnsupportedFeature( + "Multi-image generation is not supported by Stability API".to_string(), + )) +} diff --git a/video/stability/src/lib.rs b/video/stability/src/lib.rs new file mode 100644 index 000000000..9537ef1d4 --- /dev/null +++ b/video/stability/src/lib.rs @@ -0,0 +1,129 @@ +mod client; +mod conversion; + +use crate::client::StabilityApi; +use crate::conversion::{ + cancel_video_generation, extend_video, generate_lip_sync_video, generate_video, + generate_video_effects, list_available_voices, multi_image_generation, poll_video_generation, + upscale_video, +}; +use golem_video::config::with_config_key; +use golem_video::durability::{DurableVideo, ExtendedGuest}; +use golem_video::exports::golem::video::advanced::Guest as AdvancedGuest; +use golem_video::exports::golem::video::lip_sync::Guest as LipSyncGuest; +use golem_video::exports::golem::video::types::{ + AudioSource, BaseVideo, EffectType, GenerationConfig, InputImage, Kv, MediaInput, VideoError, + VideoResult, VoiceInfo, +}; +use golem_video::exports::golem::video::video_generation::Guest as VideoGenerationGuest; +use golem_video::LOGGING_STATE; + +struct StabilityComponent; + +impl StabilityComponent { + const ENV_VAR_NAME: &'static str = "STABILITY_API_KEY"; +} + +impl VideoGenerationGuest for StabilityComponent { + fn generate(input: MediaInput, config: GenerationConfig) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + generate_video(&client, input, config) + }) + } + + fn poll(job_id: String) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + poll_video_generation(&client, job_id) + }) + } + + fn cancel(job_id: String) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + cancel_video_generation(job_id) + } +} + +impl LipSyncGuest for StabilityComponent { + fn generate_lip_sync(video: BaseVideo, audio: AudioSource) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + generate_lip_sync_video(&client, video, audio) + }) + } + + fn list_voices(language: Option) -> Result, VideoError> { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + list_available_voices(&client, language) + }) + } +} + +impl AdvancedGuest for StabilityComponent { + fn extend_video( + video_id: String, + prompt: Option, + negative_prompt: Option, + cfg_scale: Option, + provider_options: Option>, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + extend_video( + &client, + video_id, + prompt, + negative_prompt, + cfg_scale, + provider_options, + ) + }) + } + + fn upscale_video(input: BaseVideo) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + upscale_video(&client, input) + }) + } + + fn generate_video_effects( + input: InputImage, + effect: EffectType, + model: Option, + duration: Option, + mode: Option, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + generate_video_effects(&client, input, effect, model, duration, mode) + }) + } + + fn multi_image_generation( + input_images: Vec, + prompt: Option, + config: GenerationConfig, + ) -> Result { + LOGGING_STATE.with_borrow_mut(|state| state.init()); + with_config_key(Self::ENV_VAR_NAME, Err, |api_key| { + let client = StabilityApi::new(api_key); + multi_image_generation(&client, input_images, prompt, config) + }) + } +} + +impl ExtendedGuest for StabilityComponent {} + +type DurableStabilityComponent = DurableVideo; + +golem_video::export_video!(DurableStabilityComponent with_types_in golem_video); diff --git a/video/stability/wit/deps/golem-video/golem-video.wit b/video/stability/wit/deps/golem-video/golem-video.wit new file mode 100644 index 000000000..8a2ecf138 --- /dev/null +++ b/video/stability/wit/deps/golem-video/golem-video.wit @@ -0,0 +1,256 @@ +package golem:video@1.0.0; + +interface types { + variant video-error { + invalid-input(string), + unsupported-feature(string), + quota-exceeded, + generation-failed(string), + cancelled, + internal-error(string), + } + + variant media-input { + text(string), + image(reference), + video(base-video), + } + + record reference { + data: input-image, + prompt: option, + role: option, + } + + enum image-role { + first, + last, + } + + record input-image { + data: media-data, + } + + record base-video { + data: media-data, + } + + record narration { + data: media-data, + } + + variant media-data { + url(string), + bytes(raw-bytes), + } + + record raw-bytes { + bytes: list, + mime-type: string, + } + + record static-mask { + mask: input-image, + } + + record dynamic-mask { + mask: input-image, + trajectories: list, + } + + record position { + x: u32, + y: u32, + } + + enum camera-movement { + simple, + down-back, + forward-up, + right-turn-forward, + left-turn-forward, + } + + record camera-config { + horizontal: f32, + vertical: f32, + pan: f32, + tilt: f32, + zoom: f32, + roll: f32, + } + + variant camera-control { + movement(camera-movement), + config(camera-config), + } + + record generation-config { + negative-prompt: option, + seed: option, + scheduler: option, + guidance-scale: option, + aspect-ratio: option, + duration-seconds: option, + resolution: option, + model: option, + enable-audio: option, + enhance-prompt: option, + provider-options: option>, + lastframe: option, + static-mask: option, + dynamic-mask: option, + camera-control: option, + } + + enum aspect-ratio { + square, + portrait, + landscape, + cinema, + } + + enum resolution { + sd, + hd, + fhd, + uhd, + } + + record kv { + key: string, + value: string, + } + + record video { + uri: option, + base64-bytes: option>, + mime-type: string, + width: option, + height: option, + fps: option, + duration-seconds: option, + } + + variant job-status { + pending, + running, + succeeded, + failed(string), + } + + record video-result { + status: job-status, + videos: option>, + metadata: option>, + } + + record text-to-speech { + text: string, + voice-id: string, + language: voice-language, + speed: u32, + } + + variant audio-source { + from-text(text-to-speech), + from-audio(narration), + } + + record voice-info { + voice-id: string, + name: string, + language: voice-language, + preview-url: option, + } + + enum voice-language { + en, + zh + } + + enum single-image-effects { + bloombloom, + dizzydizzy, + fuzzyfuzzy, + squish, + expansion, + } + + enum dual-image-effects { + hug, + kiss, + heart-gesture, + } + + record dual-effect { + effect: dual-image-effects, + second-image: input-image, + } + + variant effect-type { + single(single-image-effects), + dual(dual-effect), + } +} + +interface video-generation { + use types.{media-input, generation-config, video-result, video-error}; + + generate: func(input: media-input, config: generation-config) -> result; + poll: func(job-id: string) -> result; + cancel: func(job-id: string) -> result; +} + +interface lip-sync { + use types.{base-video, audio-source, video-error, voice-info}; + + generate-lip-sync: func( + video: base-video, + audio: audio-source, + ) -> result; + + list-voices: func(language: option) -> result, video-error>; +} + +interface advanced { + use types.{video-error, kv, base-video, generation-config, input-image, effect-type}; + + extend-video: func( + video-id: string, + prompt: option, + negative-prompt: option, + cfg-scale: option, + provider-options: option>, + ) -> result; + + upscale-video: func( + input: base-video, + ) -> result; + + generate-video-effects: func( + input: input-image, + effect: effect-type, + model: option, + duration: option, + mode: option, + ) -> result; + + multi-image-generation: func( + input-images: list, + prompt: option, + config: generation-config, + ) -> result; +} + +world video-library { + import types; + import video-generation; + import lip-sync; + import advanced; + + export video-generation; + export lip-sync; + export advanced; + export types; +} diff --git a/video/stability/wit/stability.wit b/video/stability/wit/stability.wit new file mode 100644 index 000000000..384246697 --- /dev/null +++ b/video/stability/wit/stability.wit @@ -0,0 +1,5 @@ +package golem:video-stability@1.0.0; + +world video-library { + include golem:video/video-library@1.0.0; +} \ No newline at end of file diff --git a/video/veo/Cargo.toml b/video/veo/Cargo.toml new file mode 100644 index 000000000..f1e18583d --- /dev/null +++ b/video/veo/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "golem-video-veo" +version = "0.0.0" +edition = "2021" +license = "Apache-2.0" +homepage = "https://golem.cloud" +repository = "https://github.com/golemcloud/golem-llm" +description = "WebAssembly component for working with Google Veo video APIs, with special support for Golem Cloud" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[features] +default = ["durability"] +durability = ["golem-rust/durability", "golem-video/durability"] + +[dependencies] +golem-video = { path = "../video", version = "0.0.0", default-features = false } + +golem-rust = { workspace = true } +log = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +wit-bindgen-rt = { workspace = true } +base64 = { workspace = true } +urlencoding = "2.1" + +# GCP authentication dependencies +rsa = "0.9" +pkcs8 = "0.10" +sha2 = "0.10" +data-encoding = "2.4" + +[package.metadata.component] +package = "golem:video-veo" + +[package.metadata.component.bindings] +generate_unused_types = true + +[package.metadata.component.bindings.with] +"golem:video/types@1.0.0" = "golem_video::golem::video::types" +"golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +"golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +"golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" + +[package.metadata.component.target] +path = "wit" + +[package.metadata.component.target.dependencies] +"golem:video" = { path = "wit/deps/golem-video" } \ No newline at end of file diff --git a/video/veo/src/authentication.rs b/video/veo/src/authentication.rs new file mode 100644 index 000000000..9099e3d5d --- /dev/null +++ b/video/veo/src/authentication.rs @@ -0,0 +1,149 @@ +use data_encoding::BASE64URL_NOPAD; +use golem_video::error::internal_error; +use golem_video::exports::golem::video::types::VideoError; +use log::{debug, trace}; +use rsa::pkcs8::DecodePrivateKey; +use rsa::RsaPrivateKey; +use serde_json::json; +use sha2::{Digest, Sha256}; +use std::time::{SystemTime, UNIX_EPOCH}; + +/// SHA-256 DigestInfo prefix for PKCS#1 v1.5 signatures (RFC 8017) +const SHA256_PREFIX: &[u8] = &[ + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, +]; + +/// Generate GCP access token using service account credentials +pub fn generate_access_token( + client_email: &str, + private_key_pem: &str, + scope: &str, +) -> Result { + trace!("Generating GCP access token for client: {client_email}"); + + // Step 1: Generate JWT + let jwt = generate_jwt(client_email, private_key_pem, scope)?; + + // Step 2: Exchange JWT for access token + exchange_jwt_for_token(&jwt) +} + +/// Generate a signed JWT for GCP authentication +fn generate_jwt( + client_email: &str, + private_key_pem: &str, + scope: &str, +) -> Result { + // Convert literal \n characters to actual newlines (like echo -e in bash) + let processed_key = private_key_pem.replace("\\n", "\n"); + + // Parse the private key + let private_key = RsaPrivateKey::from_pkcs8_pem(&processed_key) + .map_err(|e| internal_error(format!("Failed to parse private key: {e}")))?; + + // Get current time + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| internal_error(format!("Failed to get current time: {e}")))? + .as_secs(); + + // Create JWT header + let header = json!({ + "alg": "RS256", + "typ": "JWT" + }); + + // Create JWT payload + let payload = json!({ + "iss": client_email, + "scope": scope, + "aud": "https://oauth2.googleapis.com/token", + "iat": now, + "exp": now + 3600 // Valid for 1 hour + }); + + // Encode header and payload + let encoded_header = BASE64URL_NOPAD.encode( + &serde_json::to_vec(&header) + .map_err(|e| internal_error(format!("Failed to serialize header: {e}")))?, + ); + + let encoded_payload = BASE64URL_NOPAD.encode( + &serde_json::to_vec(&payload) + .map_err(|e| internal_error(format!("Failed to serialize payload: {e}")))?, + ); + + // Create signing input + let signing_input = format!("{encoded_header}.{encoded_payload}"); + + // Hash the signing input + let mut hasher = Sha256::new(); + hasher.update(signing_input.as_bytes()); + let hash = hasher.finalize(); + + // Create DigestInfo structure for PKCS#1 v1.5 (ASN.1 DER encoded) + let mut digest_info = Vec::new(); + digest_info.extend_from_slice(SHA256_PREFIX); + digest_info.extend_from_slice(&hash); + + // Sign using PKCS#1 v1.5 padding with manual DigestInfo + use rsa::pkcs1v15::Pkcs1v15Sign; + let padding = Pkcs1v15Sign::new_unprefixed(); + let signature = private_key + .sign(padding, &digest_info) + .map_err(|e| internal_error(format!("Failed to sign JWT: {e}")))?; + + let encoded_signature = BASE64URL_NOPAD.encode(&signature); + + // Assemble final JWT + let jwt = format!("{signing_input}.{encoded_signature}"); + + debug!("Generated JWT token for GCP authentication"); + Ok(jwt) +} + +/// Exchange JWT for GCP access token +fn exchange_jwt_for_token(jwt: &str) -> Result { + use reqwest::Client; + + let client = Client::builder() + .build() + .map_err(|e| internal_error(format!("Failed to create HTTP client: {e}")))?; + + // Prepare request body + let body = format!("grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={jwt}"); + + // Make request to Google's token endpoint + let response = client + .post("https://oauth2.googleapis.com/token") + .header("Content-Type", "application/x-www-form-urlencoded") + .body(body) + .send() + .map_err(|e| internal_error(format!("Failed to request access token: {e}")))?; + + let status = response.status(); + + if !status.is_success() { + let error_body = response + .text() + .map_err(|e| internal_error(format!("Failed to read error response: {e}")))?; + return Err(internal_error(format!( + "Token exchange failed with status {status}: {error_body}" + ))); + } + + // Parse response + let response_body: serde_json::Value = response + .json() + .map_err(|e| internal_error(format!("Failed to parse token response: {e}")))?; + + // Extract access token + let access_token = response_body + .get("access_token") + .and_then(|v| v.as_str()) + .ok_or_else(|| internal_error("No access_token in response"))?; + + debug!("Successfully obtained GCP access token"); + Ok(access_token.to_string()) +} diff --git a/video/veo/src/bindings.rs b/video/veo/src/bindings.rs new file mode 100644 index 000000000..0d3527a19 --- /dev/null +++ b/video/veo/src/bindings.rs @@ -0,0 +1,148 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +// * runtime_path: "wit_bindgen_rt" +// * with "golem:video/lip-sync@1.0.0" = "golem_video::golem::video::lip_sync" +// * with "golem:video/types@1.0.0" = "golem_video::golem::video::types" +// * with "golem:video/advanced@1.0.0" = "golem_video::golem::video::advanced" +// * with "golem:video/video-generation@1.0.0" = "golem_video::golem::video::video_generation" +// * generate_unused_types +use golem_video::golem::video::types as __with_name0; +use golem_video::golem::video::video_generation as __with_name1; +use golem_video::golem::video::lip_sync as __with_name2; +use golem_video::golem::video::advanced as __with_name3; +use golem_video::golem::video::types as __with_name4; +use golem_video::golem::video::video_generation as __with_name5; +use golem_video::golem::video::lip_sync as __with_name6; +use golem_video::golem::video::advanced as __with_name7; +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:golem:video-veo@1.0.0:video-library:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 5479] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xe3)\x01A\x02\x01A\x1a\ +\x01BO\x01q\x06\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-e\ +xceeded\0\0\x11generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\ +\x04\0\x0bvideo-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\ +\x02\x01p}\x01r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\ +\x02\x03url\x01s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04\ +data\x08\x04\0\x0binput-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06p\ +rompt\x0b\x04role\x0c\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0a\ +base-video\x03\0\x0f\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\ +\0\x04\0\x0bmedia-input\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\ +\x01r\x01\x04mask\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08\ +position\x03\0\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdy\ +namic-mask\x03\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn\ +-forward\x11left-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahor\ +izontalv\x08verticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\ +\0\x1e\x01q\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-contro\ +l\x03\0\x20\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspec\ +t-ratio\x03\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01\ +r\x02\x03keys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p\ +'\x01k-\x01k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed\ +(\x09scheduler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0ar\ +esolution+\x05model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\ +\x09lastframe/\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11gene\ +ration-config\x03\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime\ +-types\x05width6\x06height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01\ +q\x04\x07pending\0\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0aj\ +ob-status\x03\09\x01p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0c\ +video-result\x03\0=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\ +\x04texts\x08voice-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0\ +A\x01q\x02\x09from-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-sourc\ +e\x03\0C\x01r\x04\x08voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\ +\0\x0avoice-info\x03\0E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06sq\ +uish\x09expansion\x04\0\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0d\ +heart-gesture\x04\0\x12dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csec\ +ond-image\x0a\x04\0\x0bdual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\ +\x01\xcc\0\0\x04\0\x0beffect-type\x03\0M\x03\0\x17golem:video/types@1.0.0\x05\0\x02\ +\x03\0\0\x0bmedia-input\x02\x03\0\0\x11generation-config\x02\x03\0\0\x0cvideo-re\ +sult\x02\x03\0\0\x0bvideo-error\x01B\x10\x02\x03\x02\x01\x01\x04\0\x0bmedia-inpu\ +t\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\x03\0\x02\x02\x03\x02\x01\ +\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\ +\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06config\x03\0\x08\x04\0\x08gene\ +rate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\x0a\x04\0\x04poll\x01\x0b\ +\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x03\0\"golem:video/video-gene\ +ration@1.0.0\x05\x05\x02\x03\0\0\x0abase-video\x02\x03\0\0\x0caudio-source\x02\x03\ +\0\0\x0avoice-info\x01B\x10\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\0\x02\x03\ +\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\x02\x01\x04\x04\0\x0bvideo-\ +error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-info\x03\0\x06\x01j\x01s\x01\x05\ +\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11generate-lip-sync\x01\x09\x01\ +ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08language\x0a\0\x0c\x04\0\x0blist-vo\ +ices\x01\x0d\x03\0\x1agolem:video/lip-sync@1.0.0\x05\x09\x02\x03\0\0\x02kv\x02\x03\ +\0\0\x0binput-image\x02\x03\0\0\x0beffect-type\x01B\x1a\x02\x03\x02\x01\x04\x04\0\ +\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\ +\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\x02\x01\x02\x04\0\x11generation-confi\ +g\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0binput-image\x03\0\x08\x02\x03\x02\x01\x0c\ +\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01\ +@\x05\x08video-ids\x06prompt\x0c\x0fnegative-prompt\x0c\x09cfg-scale\x0d\x10prov\ +ider-options\x0f\0\x10\x04\0\x0cextend-video\x01\x11\x01@\x01\x05input\x05\0\x10\ +\x04\0\x0dupscale-video\x01\x12\x01@\x05\x05input\x09\x06effect\x0b\x05model\x0c\ +\x08duration\x0d\x04mode\x0c\0\x10\x04\0\x16generate-video-effects\x01\x13\x01p\x09\ +\x01@\x03\x0cinput-images\x14\x06prompt\x0c\x06config\x07\0\x10\x04\0\x16multi-i\ +mage-generation\x01\x15\x03\0\x1agolem:video/advanced@1.0.0\x05\x0d\x01BO\x01q\x06\ +\x0dinvalid-input\x01s\0\x13unsupported-feature\x01s\0\x0equota-exceeded\0\0\x11\ +generation-failed\x01s\0\x09cancelled\0\0\x0einternal-error\x01s\0\x04\0\x0bvide\ +o-error\x03\0\0\x01m\x02\x05first\x04last\x04\0\x0aimage-role\x03\0\x02\x01p}\x01\ +r\x02\x05bytes\x04\x09mime-types\x04\0\x09raw-bytes\x03\0\x05\x01q\x02\x03url\x01\ +s\0\x05bytes\x01\x06\0\x04\0\x0amedia-data\x03\0\x07\x01r\x01\x04data\x08\x04\0\x0b\ +input-image\x03\0\x09\x01ks\x01k\x03\x01r\x03\x04data\x0a\x06prompt\x0b\x04role\x0c\ +\x04\0\x09reference\x03\0\x0d\x01r\x01\x04data\x08\x04\0\x0abase-video\x03\0\x0f\ +\x01q\x03\x04text\x01s\0\x05image\x01\x0e\0\x05video\x01\x10\0\x04\0\x0bmedia-in\ +put\x03\0\x11\x01r\x01\x04data\x08\x04\0\x09narration\x03\0\x13\x01r\x01\x04mask\ +\x0a\x04\0\x0bstatic-mask\x03\0\x15\x01r\x02\x01xy\x01yy\x04\0\x08position\x03\0\ +\x17\x01p\x18\x01r\x02\x04mask\x0a\x0ctrajectories\x19\x04\0\x0cdynamic-mask\x03\ +\0\x1a\x01m\x05\x06simple\x09down-back\x0aforward-up\x12right-turn-forward\x11le\ +ft-turn-forward\x04\0\x0fcamera-movement\x03\0\x1c\x01r\x06\x0ahorizontalv\x08ve\ +rticalv\x03panv\x04tiltv\x04zoomv\x04rollv\x04\0\x0dcamera-config\x03\0\x1e\x01q\ +\x02\x08movement\x01\x1d\0\x06config\x01\x1f\0\x04\0\x0ecamera-control\x03\0\x20\ +\x01m\x04\x06square\x08portrait\x09landscape\x06cinema\x04\0\x0caspect-ratio\x03\ +\0\"\x01m\x04\x02sd\x02hd\x03fhd\x03uhd\x04\0\x0aresolution\x03\0$\x01r\x02\x03k\ +eys\x05values\x04\0\x02kv\x03\0&\x01kw\x01kv\x01k#\x01k%\x01k\x7f\x01p'\x01k-\x01\ +k\x0a\x01k\x16\x01k\x1b\x01k!\x01r\x0f\x0fnegative-prompt\x0b\x04seed(\x09schedu\ +ler\x0b\x0eguidance-scale)\x0caspect-ratio*\x10duration-seconds)\x0aresolution+\x05\ +model\x0b\x0cenable-audio,\x0eenhance-prompt,\x10provider-options.\x09lastframe/\ +\x0bstatic-mask0\x0cdynamic-mask1\x0ecamera-control2\x04\0\x11generation-config\x03\ +\03\x01k\x04\x01ky\x01r\x07\x03uri\x0b\x0cbase64-bytes5\x09mime-types\x05width6\x06\ +height6\x03fps)\x10duration-seconds)\x04\0\x05video\x03\07\x01q\x04\x07pending\0\ +\0\x07running\0\0\x09succeeded\0\0\x06failed\x01s\0\x04\0\x0ajob-status\x03\09\x01\ +p8\x01k;\x01r\x03\x06status:\x06videos<\x08metadata.\x04\0\x0cvideo-result\x03\0\ +=\x01m\x02\x02en\x02zh\x04\0\x0evoice-language\x03\0?\x01r\x04\x04texts\x08voice\ +-ids\x08language\xc0\0\x05speedy\x04\0\x0etext-to-speech\x03\0A\x01q\x02\x09from\ +-text\x01\xc2\0\0\x0afrom-audio\x01\x14\0\x04\0\x0caudio-source\x03\0C\x01r\x04\x08\ +voice-ids\x04names\x08language\xc0\0\x0bpreview-url\x0b\x04\0\x0avoice-info\x03\0\ +E\x01m\x05\x0abloombloom\x0adizzydizzy\x0afuzzyfuzzy\x06squish\x09expansion\x04\0\ +\x14single-image-effects\x03\0G\x01m\x03\x03hug\x04kiss\x0dheart-gesture\x04\0\x12\ +dual-image-effects\x03\0I\x01r\x02\x06effect\xca\0\x0csecond-image\x0a\x04\0\x0b\ +dual-effect\x03\0K\x01q\x02\x06single\x01\xc8\0\0\x04dual\x01\xcc\0\0\x04\0\x0be\ +ffect-type\x03\0M\x04\0\x17golem:video/types@1.0.0\x05\x0e\x01B\x10\x02\x03\x02\x01\ +\x01\x04\0\x0bmedia-input\x03\0\0\x02\x03\x02\x01\x02\x04\0\x11generation-config\ +\x03\0\x02\x02\x03\x02\x01\x03\x04\0\x0cvideo-result\x03\0\x04\x02\x03\x02\x01\x04\ +\x04\0\x0bvideo-error\x03\0\x06\x01j\x01s\x01\x07\x01@\x02\x05input\x01\x06confi\ +g\x03\0\x08\x04\0\x08generate\x01\x09\x01j\x01\x05\x01\x07\x01@\x01\x06job-ids\0\ +\x0a\x04\0\x04poll\x01\x0b\x01@\x01\x06job-ids\0\x08\x04\0\x06cancel\x01\x0c\x04\ +\0\"golem:video/video-generation@1.0.0\x05\x0f\x01B\x10\x02\x03\x02\x01\x06\x04\0\ +\x0abase-video\x03\0\0\x02\x03\x02\x01\x07\x04\0\x0caudio-source\x03\0\x02\x02\x03\ +\x02\x01\x04\x04\0\x0bvideo-error\x03\0\x04\x02\x03\x02\x01\x08\x04\0\x0avoice-i\ +nfo\x03\0\x06\x01j\x01s\x01\x05\x01@\x02\x05video\x01\x05audio\x03\0\x08\x04\0\x11\ +generate-lip-sync\x01\x09\x01ks\x01p\x07\x01j\x01\x0b\x01\x05\x01@\x01\x08langua\ +ge\x0a\0\x0c\x04\0\x0blist-voices\x01\x0d\x04\0\x1agolem:video/lip-sync@1.0.0\x05\ +\x10\x01B\x1a\x02\x03\x02\x01\x04\x04\0\x0bvideo-error\x03\0\0\x02\x03\x02\x01\x0a\ +\x04\0\x02kv\x03\0\x02\x02\x03\x02\x01\x06\x04\0\x0abase-video\x03\0\x04\x02\x03\ +\x02\x01\x02\x04\0\x11generation-config\x03\0\x06\x02\x03\x02\x01\x0b\x04\0\x0bi\ +nput-image\x03\0\x08\x02\x03\x02\x01\x0c\x04\0\x0beffect-type\x03\0\x0a\x01ks\x01\ +kv\x01p\x03\x01k\x0e\x01j\x01s\x01\x01\x01@\x05\x08video-ids\x06prompt\x0c\x0fne\ +gative-prompt\x0c\x09cfg-scale\x0d\x10provider-options\x0f\0\x10\x04\0\x0cextend\ +-video\x01\x11\x01@\x01\x05input\x05\0\x10\x04\0\x0dupscale-video\x01\x12\x01@\x05\ +\x05input\x09\x06effect\x0b\x05model\x0c\x08duration\x0d\x04mode\x0c\0\x10\x04\0\ +\x16generate-video-effects\x01\x13\x01p\x09\x01@\x03\x0cinput-images\x14\x06prom\ +pt\x0c\x06config\x07\0\x10\x04\0\x16multi-image-generation\x01\x15\x04\0\x1agole\ +m:video/advanced@1.0.0\x05\x11\x04\0#golem:video-veo/video-library@1.0.0\x04\0\x0b\ +\x13\x01\0\x0dvideo-library\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dw\ +it-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/video/veo/src/client.rs b/video/veo/src/client.rs new file mode 100644 index 000000000..1482ce40b --- /dev/null +++ b/video/veo/src/client.rs @@ -0,0 +1,381 @@ +use crate::authentication::generate_access_token; +use golem_video::error::{from_reqwest_error, video_error_from_status}; +use golem_video::exports::golem::video::types::VideoError; +use log::trace; +use reqwest::{Client, Method, Response}; +use serde::{Deserialize, Serialize}; + +const BASE_URL: &str = "https://us-central1-aiplatform.googleapis.com/v1"; +const SCOPE: &str = "https://www.googleapis.com/auth/cloud-platform"; + +/// The Veo API client for video generation +pub struct VeoApi { + project_id: String, + client_email: String, + private_key: String, + client: Client, +} + +impl VeoApi { + pub fn new(project_id: String, client_email: String, private_key: String) -> Self { + let client = Client::builder() + .default_headers(reqwest::header::HeaderMap::new()) + .build() + .expect("Failed to initialize HTTP client"); + Self { + project_id, + client_email, + private_key, + client, + } + } + + fn get_auth_header(&self) -> Result { + let token = generate_access_token(&self.client_email, &self.private_key, SCOPE)?; + Ok(format!("Bearer {token}")) + } + + pub fn generate_text_to_video( + &self, + request: TextToVideoRequest, + model_id: Option, + ) -> Result { + trace!("Sending text-to-video request to Veo API"); + + let auth_header = self.get_auth_header()?; + let model = model_id.as_deref().unwrap_or("veo-2.0-generate-001"); + + let response: Response = self + .client + .request( + Method::POST, + format!( + "{}/projects/{}/locations/us-central1/publishers/google/models/{}:predictLongRunning", + BASE_URL, self.project_id, model + ) + ) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + pub fn generate_image_to_video( + &self, + request: ImageToVideoRequest, + model_id: Option, + ) -> Result { + trace!("Sending image-to-video request to Veo API"); + + let auth_header = self.get_auth_header()?; + let model = model_id.as_deref().unwrap_or("veo-2.0-generate-001"); + + let response: Response = self + .client + .request( + Method::POST, + format!( + "{}/projects/{}/locations/us-central1/publishers/google/models/{}:predictLongRunning", + BASE_URL, self.project_id, model + ) + ) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&request) + .send() + .map_err(|err| from_reqwest_error("Request failed", err))?; + + parse_response(response) + } + + pub fn poll_generation(&self, operation_name: &str) -> Result { + trace!("Polling Veo API for operation: {operation_name}"); + + let auth_header = self.get_auth_header()?; + + let poll_request = PollRequest { + operation_name: operation_name.to_string(), + }; + + // The operation_name is in format: projects/.../locations/.../publishers/google/models/MODEL_ID/operations/OPERATION_ID + // We need to extract the base path and construct the fetchPredictOperation endpoint + // Note: We split on the original string since "/operations/" is a known delimiter + let parts: Vec<&str> = operation_name.split("/operations/").collect(); + if parts.len() != 2 { + trace!( + "Invalid operation name format - parts count: {}, operation_name: {}", + parts.len(), + operation_name + ); + return Err(VideoError::InternalError(format!( + "Invalid operation name format: {operation_name}" + ))); + } + + let base_path = parts[0]; // projects/.../locations/.../publishers/google/models/MODEL_ID + let fetch_url = format!( + "https://us-central1-aiplatform.googleapis.com/v1/{base_path}:fetchPredictOperation" + ); + + trace!("Constructed fetch URL: {fetch_url}"); + trace!("Poll request payload: {poll_request:?}"); + + let response: Response = self + .client + .request(Method::POST, fetch_url) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .json(&poll_request) + .send() + .map_err(|err| from_reqwest_error("Poll request failed", err))?; + + let status = response.status(); + trace!("Received response with status: {status}"); + + if status.is_success() { + // Get the raw response text first for debugging + let response_text = response + .text() + .map_err(|err| from_reqwest_error("Failed to read response text", err))?; + + trace!("Raw response JSON: {response_text}"); + + // Try to parse the JSON + let operation_response: OperationResponse = serde_json::from_str(&response_text) + .map_err(|err| { + trace!("JSON parsing failed with error: {err}"); + trace!("Failed to parse this JSON: {response_text}"); + VideoError::InternalError(format!("Failed to parse operation response: {err}")) + })?; + + trace!("Successfully parsed OperationResponse: {operation_response:?}"); + + // When the operation is still processing, the API may not include the 'done' field + // If 'done' is missing, we treat it as false (still processing) + let is_done = operation_response.done.unwrap_or(false); + trace!("Operation done status: {is_done}"); + + if !is_done { + trace!("Operation still processing, returning Processing status"); + return Ok(PollResponse::Processing); + } + + trace!("Operation completed, checking for response data"); + + if let Some(response) = operation_response.response { + trace!("Found response data: {response:?}"); + + if let Some(videos) = response.videos { + trace!("Found {} videos in response", videos.len()); + + let video_results: Result, VideoError> = videos + .into_iter() + .enumerate() + .map(|(index, video)| { + trace!("Processing video {index}: {video:?}"); + + if let Some(base64_data) = video.bytes_base64_encoded { + trace!( + "Video {} has base64 data, length: {}", + index, + base64_data.len() + ); + + // Decode base64 video data + let video_data = base64::Engine::decode( + &base64::engine::general_purpose::STANDARD, + &base64_data, + ) + .map_err(|e| { + trace!("Failed to decode base64 for video {index}: {e}"); + VideoError::InternalError(format!( + "Failed to decode base64 video data: {e}" + )) + })?; + + let mime_type = + video.mime_type.unwrap_or_else(|| "video/mp4".to_string()); + + trace!( + "Successfully decoded video {}, data length: {}, mime_type: {}", + index, + video_data.len(), + mime_type + ); + + Ok(VideoResultData { + video_data, + mime_type, + }) + } else { + trace!("Video {index} has no base64 encoded data"); + Err(VideoError::InternalError( + "No base64 encoded video data in response".to_string(), + )) + } + }) + .collect(); + + match video_results { + Ok(videos) => { + trace!("Successfully processed all {} videos", videos.len()); + Ok(PollResponse::Complete(videos)) + } + Err(e) => { + trace!("Failed to process videos: {e:?}"); + Err(e) + } + } + } else { + trace!("No videos found in successful operation response"); + Err(VideoError::InternalError( + "No videos in successful operation".to_string(), + )) + } + } else { + trace!("Operation completed but no response data found"); + Err(VideoError::GenerationFailed( + "Operation completed but no response data".to_string(), + )) + } + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to read error response", err))?; + + trace!("Request failed with status {status}, error body: {error_body}"); + Err(video_error_from_status(status, error_body)) + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct TextToVideoRequest { + pub instances: Vec, + pub parameters: VideoParameters, +} + +#[derive(Debug, Clone, Serialize)] +pub struct TextToVideoInstance { + pub prompt: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ImageToVideoRequest { + pub instances: Vec, + pub parameters: VideoParameters, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ImageToVideoInstance { + pub prompt: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub image: Option, + #[serde(rename = "lastFrame", skip_serializing_if = "Option::is_none")] + pub last_frame: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub video: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ImageData { + #[serde(rename = "bytesBase64Encoded")] + pub bytes_base64_encoded: String, + #[serde(rename = "mimeType")] + pub mime_type: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct VideoData { + #[serde(rename = "bytesBase64Encoded")] + pub bytes_base64_encoded: String, + #[serde(rename = "mimeType")] + pub mime_type: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct VideoParameters { + #[serde(rename = "aspectRatio", skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + #[serde(rename = "durationSeconds")] + pub duration_seconds: u32, + #[serde(rename = "enhancePrompt", skip_serializing_if = "Option::is_none")] + pub enhance_prompt: Option, + #[serde(rename = "generateAudio", skip_serializing_if = "Option::is_none")] + pub generate_audio: Option, + #[serde(rename = "negativePrompt", skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + #[serde(rename = "personGeneration", skip_serializing_if = "Option::is_none")] + pub person_generation: Option, + #[serde(rename = "sampleCount", skip_serializing_if = "Option::is_none")] + pub sample_count: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, + #[serde(rename = "storageUri", skip_serializing_if = "Option::is_none")] + pub storage_uri: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct PollRequest { + #[serde(rename = "operationName")] + pub operation_name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenerationResponse { + pub name: String, +} + +#[derive(Debug, Clone)] +pub enum PollResponse { + Processing, + Complete(Vec), +} + +#[derive(Debug, Clone)] +pub struct VideoResultData { + pub video_data: Vec, + pub mime_type: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OperationResponse { + pub name: String, + /// Whether the operation is complete. When still processing, this field may be absent. + pub done: Option, + pub response: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VeoResponse { + #[serde(rename = "@type")] + pub type_field: String, + pub videos: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VeoVideo { + #[serde(rename = "bytesBase64Encoded")] + pub bytes_base64_encoded: Option, + #[serde(rename = "mimeType")] + pub mime_type: Option, +} + +fn parse_response(response: Response) -> Result { + let status = response.status(); + if status.is_success() { + response + .json::() + .map_err(|err| from_reqwest_error("Failed to decode response body", err)) + } else { + let error_body = response + .text() + .map_err(|err| from_reqwest_error("Failed to receive error response body", err))?; + + let error_message = format!("Request failed with {status}: {error_body}"); + Err(video_error_from_status(status, error_message)) + } +} diff --git a/video/veo/src/conversion.rs b/video/veo/src/conversion.rs new file mode 100644 index 000000000..dc9965d92 --- /dev/null +++ b/video/veo/src/conversion.rs @@ -0,0 +1,572 @@ +use crate::client::{ + ImageData, ImageToVideoInstance, ImageToVideoRequest, PollResponse, TextToVideoInstance, + TextToVideoRequest, VeoApi, VideoData, VideoParameters, +}; +use golem_video::error::invalid_input; +use golem_video::exports::golem::video::types::{ + AspectRatio, GenerationConfig, ImageRole, JobStatus, MediaData, MediaInput, Resolution, Video, + VideoError, VideoResult, +}; +use golem_video::utils::{download_image_from_url, download_video_from_url}; +use std::collections::HashMap; + +type RequestTuple = ( + Option, + Option, + Option, +); + +pub fn media_input_to_request( + input: MediaInput, + config: GenerationConfig, +) -> Result { + // Parse provider options + let options: HashMap = config + .provider_options + .as_ref() + .map(|po| { + po.iter() + .map(|kv| (kv.key.clone(), kv.value.clone())) + .collect() + }) + .unwrap_or_default(); + + // Determine model - default to veo-2.0-generate-001, can be overridden + let model_id = config.model.clone().or_else(|| { + options + .get("model") + .cloned() + .or_else(|| Some("veo-2.0-generate-001".to_string())) + }); + + // Validate model if provided + if let Some(ref model) = model_id { + if !matches!( + model.as_str(), + "veo-2.0-generate-001" | "veo-3.0-generate-preview" + ) { + return Err(invalid_input( + "Model must be one of: veo-2.0-generate-001, veo-3.0-generate-preview", + )); + } + } + + // Determine aspect ratio + let aspect_ratio = determine_aspect_ratio(config.aspect_ratio, config.resolution)?; + + // Duration support - Veo supports 5-8 seconds for veo-2.0, 8 seconds for veo-3.0 + let duration_seconds = match config.duration_seconds { + Some(d) => { + let duration = d.round() as u32; + if model_id.as_deref() == Some("veo-3.0-generate-preview") { + 8 // veo-3.0 only supports 8 seconds + } else { + duration.clamp(5, 8) // veo-2.0 supports 5-8 seconds + } + } + None => 8, // Default to 8 seconds + }; + + // Generate audio support (required for veo-3.0) + let generate_audio = if model_id.as_deref() == Some("veo-3.0-generate-preview") { + Some(config.enable_audio.unwrap_or(false)) + } else { + None // Not supported by veo-2.0 + }; + + // Person generation setting + let person_generation = options + .get("person_generation") + .cloned() + .or_else(|| Some("allow_adult".to_string())); + if let Some(ref setting) = person_generation { + if !matches!(setting.as_str(), "allow_adult" | "dont_allow") { + return Err(invalid_input( + "person_generation must be 'allow_adult' or 'dont_allow'", + )); + } + } + + // Sample count (1-4 videos) + let sample_count = options + .get("sample_count") + .and_then(|s| s.parse::().ok()) + .map(|c| c.clamp(1, 4)); + + // Storage URI for output + let storage_uri = options.get("storage_uri").cloned(); + + let parameters = VideoParameters { + aspect_ratio: Some(aspect_ratio), + duration_seconds, + enhance_prompt: config.enhance_prompt, + generate_audio, + negative_prompt: config.negative_prompt.clone(), + person_generation, + sample_count, + seed: config.seed.map(|s| s as u32), + storage_uri, + }; + + match input { + MediaInput::Video(ref_video) => { + // Check if model supports video input - only veo-2.0-generate-001 supports video + let model_str = model_id.as_deref().unwrap_or("veo-2.0-generate-001"); + if model_str != "veo-2.0-generate-001" { + return Err(golem_video::error::unsupported_feature( + "Video-to-video is only supported by veo-2.0-generate-001 model", + )); + } + + // Extract video data from BaseVideo structure + let video_data = match ref_video.data { + MediaData::Url(url) => { + // Download video from URL and convert to base64 + let raw_bytes = download_video_from_url(&url)?; + let mime_type = if !raw_bytes.mime_type.is_empty() { + raw_bytes.mime_type.clone() + } else { + determine_video_mime_type(&url, &raw_bytes.bytes)? + }; + + VideoData { + bytes_base64_encoded: base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + &raw_bytes.bytes, + ), + mime_type, + } + } + MediaData::Bytes(raw_bytes) => { + // Use the mime type from the raw bytes, or determine from bytes if not available + let mime_type = if !raw_bytes.mime_type.is_empty() { + raw_bytes.mime_type.clone() + } else { + determine_video_mime_type_from_bytes(&raw_bytes.bytes)? + }; + + VideoData { + bytes_base64_encoded: base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + &raw_bytes.bytes, + ), + mime_type, + } + } + }; + + // Use a default prompt for video-to-video since BaseVideo doesn't have a prompt field + let prompt = "Generate a video from this video".to_string(); + + let instances = vec![ImageToVideoInstance { + prompt, + image: None, + last_frame: None, + video: Some(video_data), + }]; + let request = ImageToVideoRequest { + instances, + parameters, + }; + + // Log warnings for unsupported options + log_unsupported_options(&config, &options); + + Ok((None, Some(request), model_id)) + } + MediaInput::Text(prompt) => { + let instances = vec![TextToVideoInstance { prompt }]; + let request = TextToVideoRequest { + instances, + parameters, + }; + + // Log warnings for unsupported options + log_unsupported_options(&config, &options); + + Ok((Some(request), None, model_id)) + } + MediaInput::Image(ref_image) => { + // Extract image data from Reference structure + let image_data = match ref_image.data.data { + MediaData::Url(url) => { + // Download image from URL and convert to base64 + let raw_bytes = download_image_from_url(&url)?; + let mime_type = if !raw_bytes.mime_type.is_empty() { + raw_bytes.mime_type.clone() + } else { + determine_image_mime_type(&url, &raw_bytes.bytes)? + }; + + ImageData { + bytes_base64_encoded: base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + &raw_bytes.bytes, + ), + mime_type, + } + } + MediaData::Bytes(raw_bytes) => { + // Use the mime type from the raw bytes, or determine from bytes if not available + let mime_type = if !raw_bytes.mime_type.is_empty() { + raw_bytes.mime_type.clone() + } else if raw_bytes.bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47]) { + "image/png".to_string() + } else { + "image/jpeg".to_string() + }; + + ImageData { + bytes_base64_encoded: base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + &raw_bytes.bytes, + ), + mime_type, + } + } + }; + + // Use prompt from the reference image, or default + let prompt = ref_image + .prompt + .clone() + .unwrap_or_else(|| "Generate a video from this image".to_string()); + + // Handle image role and lastframe + let image_role = ref_image.role.as_ref(); + + // Handle lastframe - check if model supports it + let last_frame_data = if let Some(lastframe) = &config.lastframe { + // Check if we're using a model that supports lastFrame + let model_id = model_id.as_deref().unwrap_or("veo-2.0-generate-001"); + if model_id != "veo-2.0-generate-001" { + log::warn!("lastFrame is only supported by veo-2.0-generate-001 model, ignoring for {model_id}"); + None + } else { + let lastframe_image_data = match lastframe.data { + MediaData::Url(ref url) => { + let raw_bytes = download_image_from_url(url)?; + let mime_type = if !raw_bytes.mime_type.is_empty() { + raw_bytes.mime_type.clone() + } else { + determine_image_mime_type(url, &raw_bytes.bytes)? + }; + ImageData { + bytes_base64_encoded: base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + &raw_bytes.bytes, + ), + mime_type, + } + } + MediaData::Bytes(ref raw_bytes) => { + let mime_type = if !raw_bytes.mime_type.is_empty() { + raw_bytes.mime_type.clone() + } else if raw_bytes.bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47]) { + "image/png".to_string() + } else { + "image/jpeg".to_string() + }; + ImageData { + bytes_base64_encoded: base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + &raw_bytes.bytes, + ), + mime_type, + } + } + }; + Some(lastframe_image_data) + } + } else { + None + }; + + // Handle image role for positioning + let (image_for_first, last_frame_final) = match image_role { + Some(ImageRole::Last) => { + // If image role is "last", use it as lastFrame instead + (None, Some(image_data)) + } + Some(ImageRole::First) | None => { + // Use as first frame (default behavior) + (Some(image_data), last_frame_data) + } + }; + + // Ensure we have at least one image + let final_image = image_for_first.unwrap_or_else(|| { + // If we don't have a first frame but have a last frame, create a dummy first frame + log::warn!("No first frame provided, using a placeholder. Consider providing both first and last frames."); + ImageData { + bytes_base64_encoded: String::new(), + mime_type: "image/jpeg".to_string(), + } + }); + + let instances = vec![ImageToVideoInstance { + prompt, + image: Some(final_image), + last_frame: last_frame_final, + video: None, + }]; + let request = ImageToVideoRequest { + instances, + parameters, + }; + + // Log warnings for unsupported options + log_unsupported_options(&config, &options); + + Ok((None, Some(request), model_id)) + } + } +} + +fn determine_aspect_ratio( + aspect_ratio: Option, + _resolution: Option, +) -> Result { + let target_aspect = aspect_ratio.unwrap_or(AspectRatio::Landscape); + + match target_aspect { + AspectRatio::Landscape => Ok("16:9".to_string()), + AspectRatio::Portrait => Ok("9:16".to_string()), + AspectRatio::Square => { + log::warn!("Square aspect ratio not supported by Veo, using 16:9"); + Ok("16:9".to_string()) + } + AspectRatio::Cinema => { + log::warn!("Cinema aspect ratio not directly supported by Veo, using 16:9"); + Ok("16:9".to_string()) + } + } +} + +fn determine_image_mime_type(url: &str, bytes: &[u8]) -> Result { + // Check file extension first + if url.to_lowercase().ends_with(".png") { + return Ok("image/png".to_string()); + } + if url.to_lowercase().ends_with(".jpg") || url.to_lowercase().ends_with(".jpeg") { + return Ok("image/jpeg".to_string()); + } + + // Check magic bytes + if bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47]) { + Ok("image/png".to_string()) + } else if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) { + Ok("image/jpeg".to_string()) + } else { + // Default to JPEG + log::warn!("Could not determine image type, defaulting to JPEG"); + Ok("image/jpeg".to_string()) + } +} + +fn determine_video_mime_type(url: &str, bytes: &[u8]) -> Result { + // Check file extension first + if url.to_lowercase().ends_with(".mp4") { + return Ok("video/mp4".to_string()); + } + if url.to_lowercase().ends_with(".mov") { + return Ok("video/quicktime".to_string()); + } + if url.to_lowercase().ends_with(".avi") { + return Ok("video/x-msvideo".to_string()); + } + if url.to_lowercase().ends_with(".webm") { + return Ok("video/webm".to_string()); + } + + // Check magic bytes + determine_video_mime_type_from_bytes(bytes) +} + +fn determine_video_mime_type_from_bytes(bytes: &[u8]) -> Result { + // Check magic bytes for common video formats + if bytes.len() >= 4 { + // MP4 format magic bytes + if bytes[4..8] == [0x66, 0x74, 0x79, 0x70] { + return Ok("video/mp4".to_string()); + } + // Check for other MP4 variants + if bytes.len() >= 8 && &bytes[0..4] == b"ftyp" { + return Ok("video/mp4".to_string()); + } + // WebM format magic bytes + if bytes[0..4] == [0x1A, 0x45, 0xDF, 0xA3] { + return Ok("video/webm".to_string()); + } + // AVI format magic bytes + if bytes[0..4] == [0x52, 0x49, 0x46, 0x46] && bytes.len() >= 12 && &bytes[8..12] == b"AVI " + { + return Ok("video/x-msvideo".to_string()); + } + // MOV format magic bytes (QuickTime) + if bytes.len() >= 8 && &bytes[4..8] == b"moov" { + return Ok("video/quicktime".to_string()); + } + } + + // Default to MP4 if we can't determine the format + log::warn!("Could not determine video type, defaulting to MP4"); + Ok("video/mp4".to_string()) +} + +fn log_unsupported_options(config: &GenerationConfig, options: &HashMap) { + if config.scheduler.is_some() { + log::warn!("scheduler is not supported by Veo API and will be ignored"); + } + if config.guidance_scale.is_some() { + log::warn!("guidance_scale is not supported by Veo API and will be ignored"); + } + if config.static_mask.is_some() { + log::warn!("static_mask is not supported by Veo API and will be ignored"); + } + if config.dynamic_mask.is_some() { + log::warn!("dynamic_mask is not supported by Veo API and will be ignored"); + } + if config.camera_control.is_some() { + log::warn!("camera_control is not supported by Veo API and will be ignored"); + } + + // Log unused provider options + for key in options.keys() { + if !matches!( + key.as_str(), + "model" | "person_generation" | "sample_count" | "storage_uri" + ) { + log::warn!("Provider option '{key}' is not supported by Veo API"); + } + } +} + +pub fn generate_video( + client: &VeoApi, + input: MediaInput, + config: GenerationConfig, +) -> Result { + let (text_request, image_request, model_id) = media_input_to_request(input, config)?; + + if let Some(request) = text_request { + let response = client.generate_text_to_video(request, model_id)?; + Ok(response.name) + } else if let Some(request) = image_request { + let response = client.generate_image_to_video(request, model_id)?; + Ok(response.name) + } else { + Err(VideoError::InternalError( + "No valid request generated".to_string(), + )) + } +} + +pub fn poll_video_generation( + client: &VeoApi, + operation_name: String, +) -> Result { + match client.poll_generation(&operation_name) { + Ok(PollResponse::Processing) => Ok(VideoResult { + status: JobStatus::Running, + videos: None, + metadata: None, + }), + Ok(PollResponse::Complete(video_results)) => { + let videos: Vec