├── .github └── workflows │ └── vapoursynth.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build ├── index.html ├── run-tests.py ├── script.sh └── windows-script.ps1 ├── clippy.toml ├── sample-plugin ├── .gitignore ├── Cargo.toml ├── src │ ├── bin │ │ └── test.rs │ └── lib.rs └── test-vpy │ ├── argument-test.vpy │ ├── invert.vpy │ ├── make_random_noise.vpy │ ├── passthrough.vpy │ └── random_noise.vpy ├── vapoursynth-sys ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── source-files │ ├── generate-bindings.sh │ └── wrapper.h └── src │ ├── bindings.rs │ └── lib.rs └── vapoursynth ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── vpy-info.rs └── vspipe.rs ├── src ├── api.rs ├── component.rs ├── core.rs ├── format.rs ├── frame.rs ├── function.rs ├── lib.rs ├── map │ ├── errors.rs │ ├── iterators.rs │ ├── mod.rs │ └── value.rs ├── node │ ├── errors.rs │ └── mod.rs ├── plugin.rs ├── plugins │ ├── ffi.rs │ ├── frame_context.rs │ └── mod.rs ├── tests.rs ├── video_info.rs └── vsscript │ ├── environment.rs │ ├── errors.rs │ └── mod.rs └── test-vpy ├── alpha.vpy ├── gradient.vpy ├── green.vpy ├── pixel-formats.vpy └── variable.vpy /.github/workflows/vapoursynth.yml: -------------------------------------------------------------------------------- 1 | name: vapoursynth 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | clippy-rustfmt: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Install Rust 11 | uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | components: clippy, rustfmt 17 | - name: Run rustfmt 18 | uses: actions-rs/cargo@v1 19 | with: 20 | command: fmt 21 | args: --all -- --check 22 | - name: Run clippy on all tests 23 | uses: actions-rs/clippy-check@v1 24 | with: 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | args: --all --all-targets --all-features --exclude sample-plugin 27 | - name: Run clippy on the sample-plugin 28 | uses: actions-rs/clippy-check@v1 29 | with: 30 | token: ${{ secrets.GITHUB_TOKEN }} 31 | args: --all-targets --package sample-plugin 32 | name: sample-plugin 33 | 34 | unix-tests: 35 | strategy: 36 | fail-fast: false 37 | 38 | matrix: 39 | toolchain: 40 | # I can't figure out how to install an older version of VapourSynth on macOS, 41 | # so don't test macOS until we can test on the latest version again. 42 | # - stable-x86_64-apple-darwin 43 | - stable-x86_64-unknown-linux-gnu 44 | - stable-i686-unknown-linux-gnu 45 | include: 46 | # - toolchain: stable-x86_64-apple-darwin 47 | # os: macOS-10.15 48 | - toolchain: stable-x86_64-unknown-linux-gnu 49 | os: ubuntu-20.04 50 | - toolchain: stable-i686-unknown-linux-gnu 51 | os: ubuntu-20.04 52 | 53 | runs-on: ${{ matrix.os }} 54 | 55 | steps: 56 | - uses: actions/checkout@v1 57 | - name: Install VapourSynth on MacOS 58 | if: matrix.os == 'macOS-10.15' 59 | run: | 60 | brew install vapoursynth 61 | - name: Install VapourSynth on Ubuntu 64-bit 62 | if: matrix.toolchain == 'stable-x86_64-unknown-linux-gnu' 63 | run: | 64 | build/script.sh 65 | - name: Install VapourSynth on Ubuntu 32-bit 66 | if: matrix.toolchain == 'stable-i686-unknown-linux-gnu' 67 | run: | 68 | build/script.sh i686 69 | - name: Install Rust 70 | uses: actions-rs/toolchain@v1 71 | with: 72 | profile: minimal 73 | toolchain: ${{ matrix.toolchain }} 74 | override: true 75 | - name: Run tests 76 | run: | 77 | cd vapoursynth 78 | python3 ../build/run-tests.py 79 | - name: Run sample-plugin tests 80 | run: | 81 | cd sample-plugin 82 | cargo build --verbose 83 | cargo run --verbose --bin test --features "cfg-if \ 84 | vapoursynth/vapoursynth-functions \ 85 | vapoursynth/vsscript-functions" 86 | - name: Run doc 87 | uses: actions-rs/cargo@v1 88 | with: 89 | command: doc 90 | args: --all-features --verbose 91 | - name: Copy index into the target directory 92 | if: matrix.toolchain == 'stable-x86_64-unknown-linux-gnu' 93 | run: | 94 | cp build/index.html target/doc 95 | - name: Deploy documentation 96 | if: > 97 | matrix.toolchain == 'stable-x86_64-unknown-linux-gnu' && 98 | github.event_name == 'push' && 99 | github.ref == 'refs/heads/master' 100 | uses: peaceiris/actions-gh-pages@v3 101 | with: 102 | github_token: ${{ secrets.GITHUB_TOKEN }} 103 | publish_dir: ./target/doc 104 | 105 | windows-tests: 106 | strategy: 107 | fail-fast: false 108 | 109 | matrix: 110 | toolchain: 111 | - stable-x86_64-pc-windows-msvc 112 | - stable-x86_64-pc-windows-gnu 113 | - stable-i686-pc-windows-msvc 114 | - stable-i686-pc-windows-gnu 115 | include: 116 | - toolchain: stable-x86_64-pc-windows-msvc 117 | arch: x86_64 118 | - toolchain: stable-x86_64-pc-windows-gnu 119 | arch: x86_64 120 | - toolchain: stable-i686-pc-windows-msvc 121 | arch: i686 122 | - toolchain: stable-i686-pc-windows-gnu 123 | arch: i686 124 | 125 | runs-on: windows-2019 126 | 127 | steps: 128 | - uses: actions/checkout@v1 129 | - name: Install VapourSynth for Windows 64-bit 130 | if: matrix.arch == 'x86_64' 131 | run: | 132 | build/windows-script.ps1 133 | - name: Install VapourSynth for Windows 32-bit 134 | if: matrix.arch == 'i686' 135 | run: | 136 | build/windows-script.ps1 -arch i686 137 | - run: rustup set auto-self-update disable 138 | - name: Install Rust 139 | uses: actions-rs/toolchain@v1 140 | with: 141 | profile: minimal 142 | toolchain: ${{ matrix.toolchain }} 143 | override: true 144 | - name: Run tests 145 | run: | 146 | $Env:Path += ";C:\Program Files\VapourSynth;" 147 | cd vapoursynth 148 | python ../build/run-tests.py 149 | - name: Build sample-plugin 150 | run: | 151 | $Env:Path += ";C:\Program Files\VapourSynth;" 152 | cd sample-plugin 153 | cargo build --verbose 154 | - name: Run sample-plugin tests 155 | # https://github.com/rust-lang/rust/issues/50176 156 | if: matrix.toolchain != 'stable-i686-pc-windows-gnu' 157 | run: | 158 | $Env:Path += ";C:\Program Files\VapourSynth;" 159 | cd sample-plugin 160 | cargo run --verbose --bin test --features "cfg-if ` 161 | vapoursynth/vapoursynth-functions ` 162 | vapoursynth/vsscript-functions" 163 | - name: Run doc 164 | uses: actions-rs/cargo@v1 165 | with: 166 | command: doc 167 | args: --all-features --verbose 168 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | vapoursynth-sys/target/ 4 | vapoursynth/target/ 5 | **/*.rs.bk 6 | Cargo.lock 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vapoursynth-sys/vapoursynth"] 2 | path = vapoursynth-sys/vapoursynth 3 | url = https://github.com/vapoursynth/vapoursynth.git 4 | [submodule "vapoursynth-sys/source-files/vapoursynth"] 5 | path = vapoursynth-sys/source-files/vapoursynth 6 | url = https://github.com/vapoursynth/vapoursynth.git 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "vapoursynth", 5 | "vapoursynth-sys", 6 | "sample-plugin", 7 | ] 8 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Ivan Molodetskikh 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vapoursynth-rs 2 | 3 | [![crates.io](https://img.shields.io/crates/v/vapoursynth.svg)](https://crates.io/crates/vapoursynth) 4 | [![Documentation](https://docs.rs/vapoursynth/badge.svg)](https://docs.rs/vapoursynth) 5 | [![Actions Status](https://github.com/YaLTeR/vapoursynth-rs/workflows/vapoursynth/badge.svg)](https://github.com/YaLTeR/vapoursynth-rs/actions) 6 | 7 | [ChangeLog](https://github.com/YaLTeR/vapoursynth-rs/blob/master/vapoursynth/CHANGELOG.md) 8 | 9 | [Documentation for the master branch with all features enabled](https://yalter.github.io/vapoursynth-rs) 10 | 11 | A safe wrapper for [VapourSynth](https://github.com/vapoursynth/vapoursynth), written in Rust. 12 | 13 | The primary goal is safety (that is, safe Rust code should not trigger undefined behavior), and secondary goals include performance and ease of use. 14 | 15 | ## Functionality 16 | 17 | Most of the VapourSynth API is covered. It's possible to evaluate `.vpy` scripts, access their properties and output, retrieve frames; enumerate loaded plugins and invoke their functions as well as create VapourSynth filters. 18 | 19 | For an example usage see [examples/vspipe.rs](https://github.com/YaLTeR/vapoursynth-rs/blob/master/vapoursynth/examples/vspipe.rs), a complete reimplementation of VapourSynth's [vspipe](https://github.com/vapoursynth/vapoursynth/blob/master/src/vspipe/vspipe.cpp) in safe Rust utilizing this crate. 20 | 21 | For a VapourSynth plugin example see [sample-plugin](https://github.com/YaLTeR/vapoursynth-rs/blob/master/sample-plugin) which implements some simple filters. 22 | 23 | ## vapoursynth-sys 24 | 25 | [![crates.io](https://img.shields.io/crates/v/vapoursynth-sys.svg)](https://crates.io/crates/vapoursynth-sys) 26 | [![Documentation](https://docs.rs/vapoursynth-sys/badge.svg)](https://docs.rs/vapoursynth-sys) 27 | 28 | [ChangeLog](https://github.com/YaLTeR/vapoursynth-rs/blob/master/vapoursynth-sys/CHANGELOG.md) 29 | 30 | Raw bindings to [VapourSynth](https://github.com/vapoursynth/vapoursynth). 31 | 32 | ## Supported Versions 33 | 34 | All VapourSynth and VSScript API versions starting with 3.0 are supported. By default the crates use the 3.0 feature set. To enable higher API version support, enable one of the following Cargo features: 35 | 36 | * `vapoursynth-api-31` for VapourSynth API 3.1 (R26) 37 | * `vapoursynth-api-32` for VapourSynth API 3.2 (R27) 38 | * `vapoursynth-api-33` for VapourSynth API 3.3 (R30) 39 | * `vapoursynth-api-34` for VapourSynth API 3.4 (R30) 40 | * `vapoursynth-api-35` for VapourSynth API 3.5 (R38) 41 | * `vapoursynth-api-36` for VapourSynth API 3.6 (R47) 42 | * `vsscript-api-31` for VSScript API 3.1 43 | * `vsscript-api-32` for VSScript API 3.2 44 | 45 | To enable linking to VapourSynth or VSScript functions, enable the following Cargo features: 46 | 47 | * `vapoursynth-functions` for VapourSynth functions (`getVapourSynthAPI()`) 48 | * `vsscript-functions` for VSScript functions (`vsscript_*()`) 49 | 50 | ## Building 51 | 52 | Make sure you have the corresponding libraries available if you enable the linking features. You can use the `VAPOURSYNTH_LIB_DIR` environment variable to specify a custom directory with the library files. 53 | 54 | On Windows the easiest way is to use the VapourSynth installer (make sure the VapourSynth SDK is checked). The crate should pick up the library directory automatically. If it doesn't or if you're cross-compiling, set `VAPOURSYNTH_LIB_DIR` to `\sdk\lib64` or `<...>\lib32`, depending on the target bitness. 55 | 56 | ## License 57 | 58 | Licensed under either of 59 | 60 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 61 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 62 | 63 | at your option. 64 | -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vapoursynth-rs documentation 6 | 7 | 8 | vapoursynth
9 | vapoursynth-sys 10 | 11 | 12 | -------------------------------------------------------------------------------- /build/run-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import itertools 4 | import os 5 | import subprocess 6 | import sys 7 | 8 | if __name__ == "__main__": 9 | VS_API_VERSIONS = ["vapoursynth-api-" + str(v) for v in range(31, 37)] 10 | VSSCRIPT_API_VERSIONS = ["vsscript-api-" + str(v) for v in range(31, 33)] 11 | VAPOURSYNTH_FUNCTIONS = ["vapoursynth-functions"] 12 | VSSCRIPT_FUNCTIONS = ["vsscript-functions"] 13 | F16_PIXEL_TYPE = ["f16-pixel-type"] 14 | 15 | features = [ 16 | VS_API_VERSIONS, 17 | VSSCRIPT_API_VERSIONS, 18 | VAPOURSYNTH_FUNCTIONS, 19 | VSSCRIPT_FUNCTIONS, 20 | F16_PIXEL_TYPE, 21 | ] 22 | 23 | for f in features: 24 | f += [""] 25 | 26 | for features in itertools.product(*features): 27 | features_string = str.join(" ", features) 28 | print("Starting tests with features: " + features_string) 29 | sys.stdout.flush() 30 | 31 | try: 32 | subprocess.run( 33 | ["cargo", "test", "--verbose", "--features", features_string], 34 | check=True, 35 | ) 36 | except subprocess.CalledProcessError: 37 | print(features_string + " failed. Exiting with code 1.") 38 | sys.exit(1) 39 | -------------------------------------------------------------------------------- /build/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | 4 | # Install nasm 5 | sudo apt-get install nasm 6 | 7 | # Install a 32-bit environment if a 32-bit arch is used 8 | if [ "$1" = "i686" ]; then 9 | sudo dpkg --add-architecture i386 10 | sudo apt-get update 11 | sudo apt-get install gcc-multilib g++-multilib libpython3.8-dev:i386 12 | fi 13 | 14 | # Install Cython 15 | sudo pip3 install cython 16 | 17 | # Change the configure arguments according to the architecture 18 | if [ "$1" = "i686" ]; then 19 | CONFIGURE_ARGS="--build=i686-linux-gnu \ 20 | CFLAGS=-m32 CXXFLAGS=-m32 LDFLAGS=-m32" 21 | else 22 | CONFIGURE_ARGS="" 23 | fi 24 | 25 | # Install zimg 26 | git clone --depth 1 --branch release-3.0.3 https://github.com/sekrit-twc/zimg.git 27 | cd zimg 28 | ./autogen.sh 29 | ./configure $CONFIGURE_ARGS 30 | sudo make install 31 | 32 | cd .. 33 | 34 | # Install VapourSynth 35 | git clone --depth 1 --branch R53 https://github.com/vapoursynth/vapoursynth.git vs-dir 36 | cd vs-dir 37 | ./autogen.sh 38 | ./configure $CONFIGURE_ARGS 39 | sudo make install 40 | 41 | cd .. 42 | 43 | # Set VapourSynth environment 44 | sudo ldconfig /usr/local/lib 45 | PYTHON3_LOCAL_LIB_PATH=$(echo /usr/local/lib/python3.*) 46 | SITE=$PYTHON3_LOCAL_LIB_PATH/site-packages/vapoursynth.so 47 | DIST=$PYTHON3_LOCAL_LIB_PATH/dist-packages/vapoursynth.so 48 | sudo ln -s "$SITE" "$DIST" 49 | -------------------------------------------------------------------------------- /build/windows-script.ps1: -------------------------------------------------------------------------------- 1 | # Get the arch and dir 2 | param([String]$arch="x86_64") 3 | 4 | $NAME = "VapourSynth" 5 | $PY_DIR = "py-dir" 6 | $VS_DIR = "vs-dir" 7 | 8 | If ($arch -eq "i686") { 9 | $SUFFIX = 32 10 | $PYTHON_PKG = "python-3.9.10-embed-win32.zip" 11 | } Else { 12 | $SUFFIX = 64 13 | $PYTHON_PKG = "python-3.9.10-embed-amd64.zip" 14 | } 15 | 16 | # Download Python embeddable and VapourSynth portable 17 | $VS_PATH = "https://github.com/vapoursynth/vapoursynth/releases/download/R53" 18 | curl -LO "https://www.python.org/ftp/python/3.9.10/$PYTHON_PKG" 19 | curl -LO "$VS_PATH/VapourSynth$SUFFIX-Portable-R53.7z" 20 | 21 | # Unzip Python embeddable and VapourSynth portable 22 | 7z x "$PYTHON_PKG" -o"$PY_DIR" 23 | 7z x "VapourSynth$SUFFIX-Portable-R53.7z" -o"$VS_DIR" 24 | 25 | # Move all VapourSynth files inside the Python ones 26 | Move-Item -Force -Path "$VS_DIR\*" -Destination "$PY_DIR" 27 | 28 | # Move the VapourSynth directory into a system directory 29 | Move-Item -Path "$PY_DIR" -Destination "C:\Program Files" 30 | Rename-Item -Path "C:\Program Files\$PY_DIR" -NewName "$NAME" 31 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["AviSynth", "VapourSynth", "VSScript"] 2 | -------------------------------------------------------------------------------- /sample-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /sample-plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sample-plugin" 3 | edition = "2021" 4 | version = "0.1.0" 5 | authors = ["Ivan Molodetskikh "] 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | cfg-if = { version = "1.0.0", optional = true } 12 | anyhow = "1.0.58" 13 | rand = "0.8.5" 14 | vapoursynth = { path = "../vapoursynth" } 15 | 16 | [[bin]] 17 | name = "test" 18 | required-features = [ 19 | "cfg-if", 20 | "vapoursynth/vapoursynth-functions", 21 | "vapoursynth/vsscript-functions", 22 | ] 23 | -------------------------------------------------------------------------------- /sample-plugin/src/bin/test.rs: -------------------------------------------------------------------------------- 1 | /// A test executable for the sample plugin. 2 | #[macro_use] 3 | extern crate cfg_if; 4 | extern crate vapoursynth; 5 | use vapoursynth::prelude::*; 6 | use vapoursynth::video_info::Framerate; 7 | 8 | use std::env::current_exe; 9 | use std::fmt::Debug; 10 | use std::io::{stdout, Write}; 11 | 12 | cfg_if! { 13 | if #[cfg(windows)] { 14 | const EXTENSION: &str = "dll"; 15 | const PREFIX: &str = ""; 16 | } else if #[cfg(target_os = "macos")] { 17 | const EXTENSION: &str = "dylib"; 18 | const PREFIX: &str = "lib"; 19 | } else { 20 | const EXTENSION: &str = "so"; 21 | const PREFIX: &str = "lib"; 22 | } 23 | } 24 | 25 | fn plugin_path() -> Vec { 26 | let mut path = current_exe().unwrap(); 27 | path.set_file_name(format!("{}sample_plugin.{}", PREFIX, EXTENSION)); 28 | path.into_os_string().into_string().unwrap().into_bytes() 29 | } 30 | 31 | fn make_environment() -> Environment { 32 | let env = Environment::new().unwrap(); 33 | 34 | // Set the running_from_test variable. 35 | let api = API::get().unwrap(); 36 | let mut map = OwnedMap::new(api); 37 | map.set("running_from_test", &1).unwrap(); 38 | env.set_variables(&map).unwrap(); 39 | 40 | // Load the required sample filter. 41 | { 42 | let core = env.get_core().unwrap(); 43 | let std = core 44 | .get_plugin_by_id("com.vapoursynth.std") 45 | .unwrap() 46 | .unwrap(); 47 | 48 | map.clear(); 49 | map.set("path", &&plugin_path()[..]).unwrap(); 50 | let rv = std.invoke("LoadPlugin", &map).unwrap(); 51 | assert_eq!(rv.error(), None); 52 | } 53 | 54 | env 55 | } 56 | 57 | fn verify_pixels(frame: &Frame, expected: [T; 3]) { 58 | for plane_num in 0..3 { 59 | let expected_row = vec![expected[plane_num]; frame.width(plane_num)]; 60 | 61 | for row in 0..frame.height(plane_num) { 62 | assert_eq!(&expected_row[..], frame.plane_row(plane_num, row)); 63 | } 64 | } 65 | } 66 | 67 | fn test_passthrough() { 68 | print!("Running test_passthrough()..."); 69 | stdout().flush().unwrap(); 70 | 71 | let mut env = make_environment(); 72 | env.eval_file("test-vpy/passthrough.vpy", EvalFlags::Nothing) 73 | .unwrap(); 74 | let node = env.get_output(0).unwrap(); 75 | 76 | verify_pixels::(&node.get_frame(0).unwrap(), [1 << 6, 1 << 6, 0]); 77 | verify_pixels::(&node.get_frame(1).unwrap(), [1 << 7, 1 << 7, 0]); 78 | verify_pixels::(&node.get_frame(2).unwrap(), [1 << 8, 1 << 8, 0]); 79 | verify_pixels::(&node.get_frame(3).unwrap(), [1 << 14, 1 << 14, 0]); 80 | 81 | println!(" ok"); 82 | } 83 | 84 | fn test_invert() { 85 | print!("Running test_invert()..."); 86 | stdout().flush().unwrap(); 87 | 88 | let mut env = make_environment(); 89 | env.eval_file("test-vpy/invert.vpy", EvalFlags::Nothing) 90 | .unwrap(); 91 | let node = env.get_output(0).unwrap(); 92 | 93 | verify_pixels::( 94 | &node.get_frame(0).unwrap(), 95 | [255 - (1 << 6), 255 - (1 << 6), 255], 96 | ); 97 | verify_pixels::( 98 | &node.get_frame(1).unwrap(), 99 | [ 100 | (1 << 9) - 1 - (1 << 7), 101 | (1 << 9) - 1 - (1 << 7), 102 | (1 << 9) - 1, 103 | ], 104 | ); 105 | verify_pixels::( 106 | &node.get_frame(2).unwrap(), 107 | [ 108 | (1 << 10) - 1 - (1 << 8), 109 | (1 << 10) - 1 - (1 << 8), 110 | (1 << 10) - 1, 111 | ], 112 | ); 113 | verify_pixels::( 114 | &node.get_frame(3).unwrap(), 115 | [65535 - (1 << 14), 65535 - (1 << 14), 65535], 116 | ); 117 | 118 | println!(" ok"); 119 | } 120 | 121 | fn test_random_noise() { 122 | print!("Running test_random_noise()..."); 123 | stdout().flush().unwrap(); 124 | 125 | let mut env = make_environment(); 126 | env.eval_file("test-vpy/random_noise.vpy", EvalFlags::Nothing) 127 | .unwrap(); 128 | let node = env.get_output(0).unwrap(); 129 | 130 | assert_eq!(node.info().num_frames, 10.into()); 131 | assert_eq!( 132 | node.info().framerate, 133 | Framerate { 134 | numerator: 60, 135 | denominator: 1, 136 | } 137 | .into() 138 | ); 139 | 140 | let frame = node.get_frame(0).unwrap(); 141 | 142 | assert_eq!(frame.width(0), 320); 143 | assert_eq!(frame.height(0), 240); 144 | assert_eq!(frame.format().id(), PresetFormat::RGB24.into()); 145 | 146 | println!(" ok"); 147 | } 148 | 149 | fn test_make_random_noise() { 150 | print!("Running test_make_random_noise()..."); 151 | stdout().flush().unwrap(); 152 | 153 | let mut env = make_environment(); 154 | env.eval_file("test-vpy/make_random_noise.vpy", EvalFlags::Nothing) 155 | .unwrap(); 156 | let node = env.get_output(0).unwrap(); 157 | 158 | assert_eq!(node.info().num_frames, 10.into()); 159 | assert_eq!( 160 | node.info().framerate, 161 | Framerate { 162 | numerator: 60, 163 | denominator: 1, 164 | } 165 | .into() 166 | ); 167 | 168 | let frame = node.get_frame(0).unwrap(); 169 | 170 | assert_eq!(frame.width(0), 320); 171 | assert_eq!(frame.height(0), 240); 172 | assert_eq!(frame.format().id(), PresetFormat::RGB24.into()); 173 | 174 | println!(" ok"); 175 | } 176 | 177 | fn test_arguments() { 178 | print!("Running test_arguments()..."); 179 | stdout().flush().unwrap(); 180 | 181 | let mut env = make_environment(); 182 | 183 | // If the evaluation succeeds, the test succeeds. 184 | env.eval_file("test-vpy/argument-test.vpy", EvalFlags::Nothing) 185 | .unwrap(); 186 | 187 | println!(" ok"); 188 | } 189 | 190 | fn main() { 191 | test_passthrough(); 192 | test_invert(); 193 | test_random_noise(); 194 | test_make_random_noise(); 195 | test_arguments(); 196 | } 197 | -------------------------------------------------------------------------------- /sample-plugin/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A sample VapourSynth plugin. 2 | extern crate rand; 3 | #[macro_use] 4 | extern crate vapoursynth; 5 | 6 | use std::ffi::CStr; 7 | use std::ptr; 8 | 9 | use anyhow::{anyhow, bail, ensure, Context, Error}; 10 | 11 | use rand::Rng; 12 | use vapoursynth::core::CoreRef; 13 | use vapoursynth::format::FormatID; 14 | use vapoursynth::function::Function; 15 | use vapoursynth::map::ValueIter; 16 | use vapoursynth::node::Flags; 17 | use vapoursynth::plugins::*; 18 | use vapoursynth::prelude::*; 19 | use vapoursynth::video_info::{Framerate, Resolution, VideoInfo}; 20 | 21 | const PLUGIN_IDENTIFIER: &str = "com.example.vapoursynth-rs"; 22 | 23 | // A simple filter that passes the frames through unchanged. 24 | struct Passthrough<'core> { 25 | source: Node<'core>, 26 | } 27 | 28 | impl<'core> Filter<'core> for Passthrough<'core> { 29 | fn video_info(&self, _api: API, _core: CoreRef<'core>) -> Vec> { 30 | vec![self.source.info()] 31 | } 32 | 33 | fn get_frame_initial( 34 | &self, 35 | _api: API, 36 | _core: CoreRef<'core>, 37 | context: FrameContext, 38 | n: usize, 39 | ) -> Result>, Error> { 40 | self.source.request_frame_filter(context, n); 41 | Ok(None) 42 | } 43 | 44 | fn get_frame( 45 | &self, 46 | _api: API, 47 | _core: CoreRef<'core>, 48 | context: FrameContext, 49 | n: usize, 50 | ) -> Result, Error> { 51 | self.source 52 | .get_frame_filter(context, n) 53 | .ok_or_else(|| anyhow!("Couldn't get the source frame")) 54 | } 55 | } 56 | 57 | make_filter_function! { 58 | PassthroughFunction, "Passthrough" 59 | 60 | fn create_passthrough<'core>( 61 | _api: API, 62 | _core: CoreRef<'core>, 63 | clip: Node<'core>, 64 | ) -> Result + 'core>>, Error> { 65 | Ok(Some(Box::new(Passthrough { source: clip }))) 66 | } 67 | } 68 | 69 | // A filter that inverts the pixel values. 70 | struct Invert<'core> { 71 | source: Node<'core>, 72 | } 73 | 74 | impl<'core> Filter<'core> for Invert<'core> { 75 | fn video_info(&self, _api: API, _core: CoreRef<'core>) -> Vec> { 76 | vec![self.source.info()] 77 | } 78 | 79 | fn get_frame_initial( 80 | &self, 81 | _api: API, 82 | _core: CoreRef<'core>, 83 | context: FrameContext, 84 | n: usize, 85 | ) -> Result>, Error> { 86 | self.source.request_frame_filter(context, n); 87 | Ok(None) 88 | } 89 | 90 | fn get_frame( 91 | &self, 92 | _api: API, 93 | core: CoreRef<'core>, 94 | context: FrameContext, 95 | n: usize, 96 | ) -> Result, Error> { 97 | let frame = self 98 | .source 99 | .get_frame_filter(context, n) 100 | .ok_or_else(|| anyhow!("Couldn't get the source frame"))?; 101 | 102 | if frame.format().sample_type() == SampleType::Float { 103 | bail!("Floating point formats are not supported"); 104 | } 105 | 106 | let mut frame = FrameRefMut::copy_of(core, &frame); 107 | 108 | for plane in 0..frame.format().plane_count() { 109 | for row in 0..frame.height(plane) { 110 | assert_eq!(frame.format().sample_type(), SampleType::Integer); 111 | 112 | let bits_per_sample = frame.format().bits_per_sample(); 113 | let bytes_per_sample = frame.format().bytes_per_sample(); 114 | 115 | match bytes_per_sample { 116 | 1 => { 117 | for pixel in frame.plane_row_mut::(plane, row) { 118 | *pixel = 255 - *pixel; 119 | } 120 | } 121 | 2 => { 122 | for pixel in frame.plane_row_mut::(plane, row) { 123 | *pixel = ((1u64 << bits_per_sample) - 1) as u16 - *pixel; 124 | } 125 | } 126 | 4 => { 127 | for pixel in frame.plane_row_mut::(plane, row) { 128 | *pixel = ((1u64 << bits_per_sample) - 1) as u32 - *pixel; 129 | } 130 | } 131 | _ => unreachable!(), 132 | } 133 | } 134 | } 135 | 136 | Ok(frame.into()) 137 | } 138 | } 139 | 140 | make_filter_function! { 141 | InvertFunction, "Invert" 142 | 143 | fn create_invert<'core>( 144 | _api: API, 145 | _core: CoreRef<'core>, 146 | clip: Node<'core>, 147 | ) -> Result + 'core>>, Error> { 148 | Ok(Some(Box::new(Invert { source: clip }))) 149 | } 150 | } 151 | 152 | // A filter that outputs random noise. 153 | struct RandomNoise { 154 | format_id: FormatID, 155 | resolution: Resolution, 156 | framerate: Framerate, 157 | length: usize, 158 | } 159 | 160 | impl<'core> Filter<'core> for RandomNoise { 161 | fn video_info(&self, _api: API, core: CoreRef<'core>) -> Vec> { 162 | vec![VideoInfo { 163 | format: core.get_format(self.format_id).unwrap().into(), 164 | resolution: self.resolution.into(), 165 | framerate: self.framerate.into(), 166 | 167 | // useless for some API versions, required for others 168 | #[allow(clippy::useless_conversion)] 169 | num_frames: self.length.into(), 170 | flags: Flags::empty(), 171 | }] 172 | } 173 | 174 | fn get_frame_initial( 175 | &self, 176 | _api: API, 177 | core: CoreRef<'core>, 178 | _context: FrameContext, 179 | _n: usize, 180 | ) -> Result>, Error> { 181 | let format = core.get_format(self.format_id).unwrap(); 182 | let mut frame = 183 | unsafe { FrameRefMut::new_uninitialized(core, None, format, self.resolution) }; 184 | 185 | for plane in 0..frame.format().plane_count() { 186 | for row in 0..frame.height(plane) { 187 | assert_eq!(frame.format().sample_type(), SampleType::Integer); 188 | 189 | let bytes_per_sample = frame.format().bytes_per_sample(); 190 | 191 | let mut rng = rand::thread_rng(); 192 | 193 | match bytes_per_sample { 194 | 1 => { 195 | let data = frame.plane_row_mut::(plane, row); 196 | for col in 0..data.len() { 197 | unsafe { 198 | ptr::write(data.as_mut_ptr().add(col), rng.gen()); 199 | } 200 | } 201 | } 202 | 2 => { 203 | let data = frame.plane_row_mut::(plane, row); 204 | for col in 0..data.len() { 205 | unsafe { 206 | ptr::write(data.as_mut_ptr().add(col), rng.gen()); 207 | } 208 | } 209 | } 210 | 4 => { 211 | let data = frame.plane_row_mut::(plane, row); 212 | for col in 0..data.len() { 213 | unsafe { 214 | ptr::write(data.as_mut_ptr().add(col), rng.gen()); 215 | } 216 | } 217 | } 218 | _ => unreachable!(), 219 | } 220 | } 221 | } 222 | 223 | Ok(Some(frame.into())) 224 | } 225 | 226 | fn get_frame( 227 | &self, 228 | _api: API, 229 | _core: CoreRef<'core>, 230 | _context: FrameContext, 231 | _n: usize, 232 | ) -> Result, Error> { 233 | unreachable!() 234 | } 235 | } 236 | 237 | make_filter_function! { 238 | RandomNoiseFunction, "RandomNoise" 239 | 240 | #[allow(clippy::too_many_arguments)] 241 | fn create_random_noise<'core>( 242 | _api: API, 243 | core: CoreRef<'core>, 244 | format: i64, 245 | width: i64, 246 | height: i64, 247 | length: i64, 248 | fpsnum: i64, 249 | fpsden: i64, 250 | ) -> Result + 'core>>, Error> { 251 | let format_id = (format as i32).into(); 252 | let format = core.get_format(format_id) 253 | .ok_or_else(|| anyhow!("No such format"))?; 254 | 255 | if format.sample_type() == SampleType::Float { 256 | bail!("Floating point formats are not supported"); 257 | } 258 | 259 | if width <= 0 || width > i64::from(i32::MAX) { 260 | bail!("Invalid width"); 261 | } 262 | let width = width as usize; 263 | 264 | if height <= 0 || height > i64::from(i32::MAX) { 265 | bail!("Invalid height"); 266 | } 267 | let height = height as usize; 268 | 269 | if length <= 0 || length > i64::from(i32::MAX) { 270 | bail!("Invalid length"); 271 | } 272 | let length = length as usize; 273 | 274 | if fpsnum <= 0 { 275 | bail!("Invalid fpsnum"); 276 | } 277 | let fpsnum = fpsnum as u64; 278 | 279 | if fpsden <= 0 { 280 | bail!("Invalid fpsden"); 281 | } 282 | let fpsden = fpsden as u64; 283 | 284 | Ok(Some(Box::new(RandomNoise { 285 | format_id, 286 | resolution: Resolution { width, height }, 287 | framerate: Framerate { 288 | numerator: fpsnum, 289 | denominator: fpsden, 290 | }, 291 | length, 292 | }))) 293 | } 294 | } 295 | 296 | // A random noise function but with variable name for MakeRandomNoiseFunction. 297 | struct VariableNameRandomNoiseFunction { 298 | name: String, 299 | 300 | // So we don't have to implement args(). 301 | underlying_function: RandomNoiseFunction, 302 | } 303 | 304 | impl FilterFunction for VariableNameRandomNoiseFunction { 305 | fn name(&self) -> &str { 306 | &self.name 307 | } 308 | 309 | fn args(&self) -> &str { 310 | self.underlying_function.args() 311 | } 312 | 313 | fn create<'core>( 314 | &self, 315 | api: API, 316 | core: CoreRef<'core>, 317 | args: &Map<'core>, 318 | ) -> Result + 'core>>, Error> { 319 | self.underlying_function.create(api, core, args) 320 | } 321 | } 322 | 323 | // A filter function that makes a random noise filter function with the given name at runtime. 324 | make_filter_function! { 325 | MakeRandomNoiseFunction, "MakeRandomNoiseFilter" 326 | 327 | fn create_make_random_noise<'core>( 328 | _api: API, 329 | core: CoreRef<'core>, 330 | name: &[u8], 331 | ) -> Result + 'core>>, Error> { 332 | let name = unsafe { CStr::from_ptr(name.as_ptr() as _) }; 333 | let name = name.to_str() 334 | .context("name contains invalid UTF-8")? 335 | .to_owned(); 336 | 337 | let plugin = core.get_plugin_by_id(PLUGIN_IDENTIFIER).unwrap().unwrap(); 338 | plugin 339 | .register_function(VariableNameRandomNoiseFunction { 340 | name, 341 | underlying_function: RandomNoiseFunction::new(), 342 | }) 343 | .unwrap(); 344 | 345 | Ok(None) 346 | } 347 | } 348 | 349 | // A filter for testing different kinds of argument passing. 350 | struct ArgumentTestFilter<'core> { 351 | clip: Node<'core>, 352 | } 353 | 354 | impl<'core> Filter<'core> for ArgumentTestFilter<'core> { 355 | fn video_info(&self, _api: API, _core: CoreRef<'core>) -> Vec> { 356 | vec![self.clip.info()] 357 | } 358 | 359 | fn get_frame_initial( 360 | &self, 361 | _api: API, 362 | _core: CoreRef<'core>, 363 | context: FrameContext, 364 | n: usize, 365 | ) -> Result>, Error> { 366 | self.clip.request_frame_filter(context, n); 367 | Ok(None) 368 | } 369 | 370 | fn get_frame( 371 | &self, 372 | _api: API, 373 | _core: CoreRef<'core>, 374 | context: FrameContext, 375 | n: usize, 376 | ) -> Result, Error> { 377 | self.clip 378 | .get_frame_filter(context, n) 379 | .ok_or_else(|| anyhow!("Couldn't get the source frame")) 380 | } 381 | } 382 | 383 | make_filter_function! { 384 | ArgumentTestFilterFunction, "ArgumentTest" 385 | 386 | #[allow(clippy::too_many_arguments)] 387 | fn create_argument_test<'core>( 388 | api: API, 389 | _core: CoreRef<'core>, 390 | int: i64, 391 | float: f64, 392 | data: &[u8], 393 | node: Node<'core>, 394 | frame: FrameRef<'core>, 395 | function: Function<'core>, 396 | optional_int: Option, 397 | another_optional_int: Option, 398 | frame_array: ValueIter<'_, 'core, FrameRef<'core>>, 399 | optional_frame_array: Option>>, 400 | ) -> Result + 'core>>, Error> { 401 | let in_ = OwnedMap::new(api); 402 | let mut out = OwnedMap::new(api); 403 | function.call(&in_, &mut out); 404 | 405 | ensure!(int == 42, "{} != 42", int); 406 | #[allow(clippy::float_cmp)] 407 | { 408 | ensure!(float == 1337f64, "{} != 1337", float); 409 | } 410 | ensure!(data == &b"asd"[..], "{:?} != {:?}", data, &b"asd"[..]); 411 | 412 | // Only one frame, this is to pass tests for all API versions 413 | // Use `Node.info()` 414 | ensure!(node.get_frame(0).is_ok(), "couldn't get frame 0"); 415 | ensure!(node.get_frame(1).is_err(), "frame count > 1"); 416 | 417 | ensure!(frame.width(0) == 320, "{} != 320", frame.width(0)); 418 | ensure!(out.get::("val").map(|x| x == 10).unwrap_or(false), "Incorrect function"); 419 | ensure!(optional_int.is_some(), "optional_int is missing"); 420 | ensure!(optional_int.unwrap() == 123, "{} != 123", optional_int.unwrap()); 421 | ensure!(another_optional_int.is_none(), "another_optional_int was present"); 422 | 423 | let mut frame_array = frame_array; 424 | ensure!(frame_array.len() == 2, "{} != 2", frame_array.len()); 425 | let frame = frame_array.next().unwrap(); 426 | ensure!(frame.width(0) == 256, "{} != 256", frame.width(0)); 427 | let frame = frame_array.next().unwrap(); 428 | ensure!(frame.width(0) == 64, "{} != 64", frame.width(0)); 429 | 430 | ensure!(optional_frame_array.is_none(), "optional_frame_array was present"); 431 | 432 | Ok(Some(Box::new(ArgumentTestFilter { clip: node }))) 433 | } 434 | } 435 | 436 | export_vapoursynth_plugin! { 437 | Metadata { 438 | identifier: PLUGIN_IDENTIFIER, 439 | namespace: "vapoursynth_rs", 440 | name: "Example vapoursynth-rs Plugin", 441 | read_only: false, 442 | }, 443 | [ 444 | PassthroughFunction::new(), 445 | InvertFunction::new(), 446 | RandomNoiseFunction::new(), 447 | MakeRandomNoiseFunction::new(), 448 | ArgumentTestFilterFunction::new(), 449 | ] 450 | } 451 | -------------------------------------------------------------------------------- /sample-plugin/test-vpy/argument-test.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | try: 5 | running_from_test 6 | except NameError: 7 | core.std.LoadPlugin('../../target/debug/libsample_plugin.so') 8 | 9 | def make_frame(width): 10 | return core.std.BlankClip(width = width, 11 | height = 240, 12 | format = vs.GRAY8, 13 | length = 1, 14 | color = [0]) 15 | 16 | core.vapoursynth_rs.ArgumentTest( 17 | 42, 18 | 1337.0, 19 | "asd", 20 | make_frame(320), 21 | make_frame(320).get_frame(0), 22 | lambda: 10, 23 | optional_int = 123, 24 | frame_array = [make_frame(256).get_frame(0), make_frame(64).get_frame(0)]).set_output() 25 | -------------------------------------------------------------------------------- /sample-plugin/test-vpy/invert.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | try: 5 | running_from_test 6 | except NameError: 7 | core.std.LoadPlugin('../../target/debug/libsample_plugin.so') 8 | 9 | def make_frame(format, color): 10 | return core.std.BlankClip(width = 320, 11 | height = 240, 12 | format = format, 13 | length = 1, 14 | color = color) 15 | 16 | clip = core.std.Splice([make_frame(vs.RGB24, [2**6, 2**6, 0]), 17 | make_frame(vs.RGB27, [2**7, 2**7, 0]), 18 | make_frame(vs.RGB30, [2**8, 2**8, 0]), 19 | make_frame(vs.RGB48, [2**14, 2**14, 0])], 20 | mismatch = True) 21 | 22 | clip = core.vapoursynth_rs.Invert(clip) 23 | 24 | try: 25 | running_from_test 26 | except NameError: 27 | clip = core.resize.Lanczos(clip, format = vs.YUV444P8, matrix_s = "709") 28 | 29 | clip.set_output() 30 | -------------------------------------------------------------------------------- /sample-plugin/test-vpy/make_random_noise.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | try: 5 | running_from_test 6 | except NameError: 7 | core.std.LoadPlugin('../../target/debug/libsample_plugin.so') 8 | 9 | core.vapoursynth_rs.MakeRandomNoiseFilter("MyAwesomeRandomNoise") 10 | 11 | clip = core.vapoursynth_rs.MyAwesomeRandomNoise(width = 320, 12 | height = 240, 13 | format = vs.RGB24, 14 | length = 10, 15 | fpsnum = 60, 16 | fpsden = 1) 17 | 18 | try: 19 | running_from_test 20 | except NameError: 21 | clip = core.resize.Lanczos(clip, format = vs.YUV444P8, matrix_s = "709") 22 | 23 | clip.set_output() 24 | -------------------------------------------------------------------------------- /sample-plugin/test-vpy/passthrough.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | try: 5 | running_from_test 6 | except NameError: 7 | core.std.LoadPlugin('../../target/debug/libsample_plugin.so') 8 | 9 | def make_frame(format, color): 10 | return core.std.BlankClip(width = 320, 11 | height = 240, 12 | format = format, 13 | length = 1, 14 | color = color) 15 | 16 | clip = core.std.Splice([make_frame(vs.RGB24, [2**6, 2**6, 0]), 17 | make_frame(vs.RGB27, [2**7, 2**7, 0]), 18 | make_frame(vs.RGB30, [2**8, 2**8, 0]), 19 | make_frame(vs.RGB48, [2**14, 2**14, 0])], 20 | mismatch = True) 21 | 22 | clip = core.vapoursynth_rs.Passthrough(clip) 23 | 24 | try: 25 | running_from_test 26 | except NameError: 27 | clip = core.resize.Lanczos(clip, format = vs.YUV444P8, matrix_s = "709") 28 | 29 | clip.set_output() 30 | -------------------------------------------------------------------------------- /sample-plugin/test-vpy/random_noise.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | try: 5 | running_from_test 6 | except NameError: 7 | core.std.LoadPlugin('../../target/debug/libsample_plugin.so') 8 | 9 | clip = core.vapoursynth_rs.RandomNoise(width = 320, 10 | height = 240, 11 | format = vs.RGB24, 12 | length = 10, 13 | fpsnum = 60, 14 | fpsden = 1) 15 | 16 | try: 17 | running_from_test 18 | except NameError: 19 | clip = core.resize.Lanczos(clip, format = vs.YUV444P8, matrix_s = "709") 20 | 21 | clip.set_output() 22 | -------------------------------------------------------------------------------- /vapoursynth-sys/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "source-files/vapoursynth"] 2 | path = source-files/vapoursynth 3 | url = https://github.com/vapoursynth/vapoursynth.git 4 | -------------------------------------------------------------------------------- /vapoursynth-sys/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.4.1 (14th Sep 2024) 2 | * Added default library paths for macOS for automatic detection. 3 | 4 | ## v0.4 (12th Jul 2022) 5 | * Migrated to the 2021 edition. 6 | * Updated dependencies. 7 | 8 | ## v0.3 (22nd Oct 2019) 9 | * Added support for VapourSynth API 3.6 (R47) 10 | * Added more default VapourSynth .lib folders on Windows: the automatic 11 | detection should now work with R46 and above in various configurations 12 | * Changed some Clippy attributes into their newer versions (bumps minimum Rust) 13 | 14 | ### v0.2.1 (16th Jun 2018) 15 | - Added missing plugin-related types 16 | - Silenced some clippy warnings 17 | 18 | ## v0.2.0 (24th Mar 2018) 19 | - Added support for targetting 32-bit Windows 20 | - Added automatic detection of common Windows VapourSynth library dirs 21 | 22 | ### v0.1.4 (11th Mar 2018) 23 | - Added an AppVeyor badge 24 | - Added a link to vapoursynth-rs in the README 25 | 26 | ### v0.1.3 (18th Feb 2018) 27 | - Added a docs.rs badge to the README 28 | - Added CHANGELOG.md 29 | 30 | ### v0.1.2 (18th Feb 2018) 31 | - Changed the documentation to be hosted at docs.rs 32 | 33 | ### v0.1.1 (2nd Feb 2018) 34 | - Added new bindings from VSScript API 3.2 35 | 36 | ## v0.1.0 37 | - Initial release 38 | -------------------------------------------------------------------------------- /vapoursynth-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vapoursynth-sys" 3 | edition = "2021" 4 | version = "0.4.1" # remember to update html_root_url 5 | authors = ["Ivan Molodetskikh "] 6 | description = "Rust bindings for vapoursynth and vsscript." 7 | license = "MIT/Apache-2.0" 8 | build = "build.rs" 9 | 10 | readme = "README.md" 11 | documentation = "https://docs.rs/vapoursynth-sys" 12 | repository = "https://github.com/YaLTeR/vapoursynth-rs" 13 | keywords = ["vapoursynth", "vsscript", "video", "bindings"] 14 | categories = ["api-bindings", "external-ffi-bindings", "multimedia::video"] 15 | 16 | [badges] 17 | travis-ci = { repository = "YaLTeR/vapoursynth-rs" } 18 | appveyor = { repository = "YaLTeR/vapoursynth-rs" } 19 | 20 | [dependencies] 21 | cfg-if = "1.0.0" 22 | 23 | [features] 24 | # Features for enabling higher API versions. 25 | vapoursynth-api-31 = ["gte-vapoursynth-api-31"] 26 | vapoursynth-api-32 = ["gte-vapoursynth-api-31", "gte-vapoursynth-api-32"] 27 | vapoursynth-api-33 = [ 28 | "gte-vapoursynth-api-31", 29 | "gte-vapoursynth-api-32", 30 | "gte-vapoursynth-api-33", 31 | ] 32 | vapoursynth-api-34 = [ 33 | "gte-vapoursynth-api-31", 34 | "gte-vapoursynth-api-32", 35 | "gte-vapoursynth-api-33", 36 | "gte-vapoursynth-api-34", 37 | ] 38 | vapoursynth-api-35 = [ 39 | "gte-vapoursynth-api-31", 40 | "gte-vapoursynth-api-32", 41 | "gte-vapoursynth-api-33", 42 | "gte-vapoursynth-api-34", 43 | "gte-vapoursynth-api-35", 44 | ] 45 | vapoursynth-api-36 = [ 46 | "gte-vapoursynth-api-31", 47 | "gte-vapoursynth-api-32", 48 | "gte-vapoursynth-api-33", 49 | "gte-vapoursynth-api-34", 50 | "gte-vapoursynth-api-35", 51 | "gte-vapoursynth-api-36", 52 | ] 53 | 54 | vsscript-api-31 = ["gte-vsscript-api-31"] 55 | vsscript-api-32 = ["gte-vsscript-api-31", "gte-vsscript-api-32"] 56 | 57 | # Features for linking to certain functions. 58 | vapoursynth-functions = [] 59 | vsscript-functions = [] 60 | 61 | # Utility features, not for outside use. 62 | gte-vapoursynth-api-31 = [] 63 | gte-vapoursynth-api-32 = [] 64 | gte-vapoursynth-api-33 = [] 65 | gte-vapoursynth-api-34 = [] 66 | gte-vapoursynth-api-35 = [] 67 | gte-vapoursynth-api-36 = [] 68 | 69 | gte-vsscript-api-31 = [] 70 | gte-vsscript-api-32 = [] 71 | 72 | [package.metadata.docs.rs] 73 | features = [ 74 | "vapoursynth-api-36", 75 | "vsscript-api-32", 76 | "vapoursynth-functions", 77 | "vsscript-functions", 78 | ] 79 | -------------------------------------------------------------------------------- /vapoursynth-sys/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /vapoursynth-sys/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /vapoursynth-sys/README.md: -------------------------------------------------------------------------------- 1 | # vapoursynth-sys 2 | 3 | [![crates.io](https://img.shields.io/crates/v/vapoursynth-sys.svg)](https://crates.io/crates/vapoursynth-sys) 4 | [![Documentation](https://docs.rs/vapoursynth-sys/badge.svg)](https://docs.rs/vapoursynth-sys) 5 | 6 | [ChangeLog](https://github.com/YaLTeR/vapoursynth-rs/blob/master/vapoursynth-sys/CHANGELOG.md) 7 | 8 | Raw bindings to [VapourSynth](https://github.com/vapoursynth/vapoursynth). 9 | 10 | Check out [vapoursynth-rs](https://crates.io/crates/vapoursynth) for a safe Rust wrapper. 11 | 12 | ## Supported Versions 13 | 14 | All VapourSynth and VSScript API versions starting with 3.0 are supported. By default the crates use the 3.0 feature set. To enable higher API version support, enable one of the following Cargo features: 15 | 16 | * `vapoursynth-api-31` for VapourSynth API 3.1 (R26) 17 | * `vapoursynth-api-32` for VapourSynth API 3.2 (R27) 18 | * `vapoursynth-api-33` for VapourSynth API 3.3 (R30) 19 | * `vapoursynth-api-34` for VapourSynth API 3.4 (R30) 20 | * `vapoursynth-api-35` for VapourSynth API 3.5 (R38) 21 | * `vapoursynth-api-36` for VapourSynth API 3.6 (R47) 22 | * `vsscript-api-31` for VSScript API 3.1 23 | * `vsscript-api-32` for VSScript API 3.2 24 | 25 | To enable linking to VapourSynth or VSScript functions, enable the following Cargo features: 26 | 27 | * `vapoursynth-functions` for VapourSynth functions (`getVapourSynthAPI()`) 28 | * `vsscript-functions` for VSScript functions (`vsscript_*()`) 29 | 30 | ## Building 31 | 32 | Make sure you have the corresponding libraries available if you enable the linking features. You can use the `VAPOURSYNTH_LIB_DIR` environment variable to specify a custom directory with the library files. 33 | 34 | On Windows the easiest way is to use the VapourSynth installer (make sure the VapourSynth SDK is checked). The crate should pick up the library directory automatically. If it doesn't or if you're cross-compiling, set `VAPOURSYNTH_LIB_DIR` to `\sdk\lib64` or `<...>\lib32`, depending on the target bitness. 35 | 36 | ## License 37 | 38 | Licensed under either of 39 | 40 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 41 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 42 | 43 | at your option. 44 | 45 | -------------------------------------------------------------------------------- /vapoursynth-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | const LIBRARY_DIR_VARIABLE: &str = "VAPOURSYNTH_LIB_DIR"; 5 | 6 | fn main() { 7 | // Make sure the build script is re-run if our env variable is changed. 8 | println!("cargo:rerun-if-env-changed={}", LIBRARY_DIR_VARIABLE); 9 | 10 | // These should always be set when a build script is run 11 | let target = env::var("TARGET").unwrap(); 12 | let host = env::var("HOST").unwrap(); 13 | 14 | let targets_windows = target.contains("windows"); 15 | let targets_macos = target.contains("apple-darwin"); 16 | 17 | // Get the default library dir for some platforms. 18 | let default_library_dir = if targets_windows { 19 | get_default_windows_library_dir(&target, &host) 20 | } else if targets_macos { 21 | get_default_macos_library_dir(&target, &host) 22 | } else { 23 | vec![] 24 | }; 25 | 26 | // Library directory override or the default dir on windows. 27 | if let Ok(dir) = env::var(LIBRARY_DIR_VARIABLE) { 28 | println!("cargo:rustc-link-search=native={}", dir); 29 | } else { 30 | for dir in default_library_dir { 31 | println!("cargo:rustc-link-search=native={}", dir); 32 | } 33 | } 34 | 35 | // Handle linking to VapourSynth libs. 36 | if env::var("CARGO_FEATURE_VAPOURSYNTH_FUNCTIONS").is_ok() { 37 | println!("cargo:rustc-link-lib=vapoursynth"); 38 | } 39 | 40 | if env::var("CARGO_FEATURE_VSSCRIPT_FUNCTIONS").is_ok() { 41 | let vsscript_lib_name = if targets_windows { 42 | "vsscript" 43 | } else { 44 | "vapoursynth-script" 45 | }; 46 | 47 | println!("cargo:rustc-link-lib={}", vsscript_lib_name); 48 | } 49 | } 50 | 51 | // Returns the default library dirs on Windows. 52 | // The default dir is where the VapourSynth installer puts the libraries. 53 | fn get_default_windows_library_dir(target: &str, host: &str) -> Vec { 54 | // If the host isn't Windows we don't have %programfiles%. 55 | if !host.contains("windows") { 56 | return vec![]; 57 | } 58 | 59 | let programfiles = env::var("programfiles").into_iter(); 60 | 61 | // Add Program Files from the other bitness. This would be Program Files (x86) with a 64-bit 62 | // host and regular Program Files with a 32-bit host running on a 64-bit system. 63 | let programfiles = programfiles.chain(env::var(if host.starts_with("i686") { 64 | "programw6432" 65 | } else { 66 | "programfiles(x86)" 67 | })); 68 | 69 | let suffix = if target.starts_with("i686") { 70 | "lib32" 71 | } else { 72 | "lib64" 73 | }; 74 | 75 | programfiles 76 | .flat_map(move |programfiles| { 77 | // Use both VapourSynth and VapourSynth-32 folder names. 78 | ["", "-32"].iter().filter_map(move |vapoursynth_suffix| { 79 | let mut path = PathBuf::from(&programfiles); 80 | path.push(format!("VapourSynth{}", vapoursynth_suffix)); 81 | path.push("sdk"); 82 | path.push(suffix); 83 | path.to_str().map(|s| s.to_owned()) 84 | }) 85 | }) 86 | .collect() 87 | } 88 | 89 | // Returns the homebrew library dirs on macOS. 90 | fn get_default_macos_library_dir(target: &str, host: &str) -> Vec { 91 | // If the host is not macOS/Apple, the library dirs will be different. 92 | if !host.contains("apple-darwin") { 93 | return vec![]; 94 | } 95 | 96 | // Use $HOMEBREW_PREFIX if set and not cross-compiling 97 | if host == target { 98 | if let Ok(prefix) = env::var("HOMEBREW_PREFIX") { 99 | return vec![format!("{}/lib", prefix)]; 100 | } 101 | } 102 | 103 | // Otherwise, return the default library dir 104 | if target.starts_with("aarch64") { 105 | vec![String::from("/opt/homebrew/lib/")] 106 | } else { 107 | vec![String::from("/usr/local/homebrew/lib/")] 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /vapoursynth-sys/source-files/generate-bindings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | bindgen --whitelist-function 'getVapourSynthAPI' \ 4 | --whitelist-function 'vsscript.*' \ 5 | --whitelist-type 'VSColorFamily' \ 6 | --whitelist-type 'VSSampleType' \ 7 | --whitelist-type 'VSPresetFormat' \ 8 | --whitelist-type 'VSFilterMode' \ 9 | --whitelist-type 'VSNodeFlags' \ 10 | --whitelist-type 'VSPropTypes' \ 11 | --whitelist-type 'VSGetPropErrors' \ 12 | --whitelist-type 'VSPropAppendMode' \ 13 | --whitelist-type 'VSActivationReason' \ 14 | --whitelist-type 'VSMessageType' \ 15 | --whitelist-type 'VSEvalFlags' \ 16 | --whitelist-type 'VSInitPlugin' \ 17 | --blacklist-type '__int64_t' \ 18 | --blacklist-type '__uint8_t' \ 19 | --bitfield-enum 'VSNodeFlags' \ 20 | --rustified-enum 'VS[^N].*' \ 21 | --no-layout-tests \ 22 | -o bindings.rs \ 23 | vapoursynth/include/VSScript.h \ 24 | -- -target x86_64-unknown-windows-unknown 25 | -------------------------------------------------------------------------------- /vapoursynth-sys/source-files/wrapper.h: -------------------------------------------------------------------------------- 1 | #include "vapoursynth/include/VSScript.h" 2 | -------------------------------------------------------------------------------- /vapoursynth-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Raw bindings to [VapourSynth](https://github.com/vapoursynth/vapoursynth). 2 | #![doc(html_root_url = "https://docs.rs/vapoursynth-sys/0.4.1")] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | #![allow(non_upper_case_globals)] 6 | 7 | #[macro_use] 8 | extern crate cfg_if; 9 | 10 | mod bindings; 11 | pub use crate::bindings::*; 12 | 13 | macro_rules! api_version { 14 | ($major:expr, $minor:expr) => { 15 | ($major << 16) | $minor 16 | }; 17 | } 18 | 19 | cfg_if! { 20 | if #[cfg(feature="vapoursynth-api-36")] { 21 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 6); 22 | } else if #[cfg(feature="vapoursynth-api-35")] { 23 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 5); 24 | } else if #[cfg(feature="vapoursynth-api-34")] { 25 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 4); 26 | } else if #[cfg(feature="vapoursynth-api-33")] { 27 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 3); 28 | } else if #[cfg(feature="vapoursynth-api-32")] { 29 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 2); 30 | } else if #[cfg(feature="vapoursynth-api-31")] { 31 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 1); 32 | } else { 33 | pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 0); 34 | } 35 | } 36 | 37 | cfg_if! { 38 | if #[cfg(feature="vsscript-api-32")] { 39 | pub const VSSCRIPT_API_VERSION: i32 = api_version!(3, 2); 40 | } else if #[cfg(feature="vsscript-api-31")] { 41 | pub const VSSCRIPT_API_VERSION: i32 = api_version!(3, 1); 42 | } else { 43 | pub const VSSCRIPT_API_VERSION: i32 = api_version!(3, 0); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vapoursynth/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.4 (12th Jul 2022) 2 | * Migrated from `failure` to `thiserror` and `anyhow`. 3 | * Migrated to the 2021 edition. 4 | * Added `impl From for i32`. 5 | * Changed integer values of the `ColorFamily` enum to match those used by VapourSynth. 6 | * Updated dependencies. 7 | 8 | ## v0.3 (22nd Oct 2019) 9 | * Added support for VapourSynth API 3.6 (R47): 10 | * `API::{add,remove}_message_handler` deprecate `API::set_message_handler`. 11 | * `CoreRef::info()` is implemented via the thread-safe `getCoreInfo2` 12 | automatically when using the `vapoursynth-api-36` feature. 13 | * Added `CoreRef::set_{max_cache_size,thread_count}`, which can now be 14 | implemented safely. 15 | * Added `API::create_core`. 16 | * Added more default VapourSynth .lib folders on Windows: the automatic 17 | detection should now work with R46 and above in various configurations. 18 | * Changed some Clippy and Rustfmt attributes into their newer versions, fixed 19 | deprecation warnings (bumps minimum Rust). 20 | * Fixed the lifetimes on the return values of 21 | `CoreRef::{get_plugin_by_id,get_plugin_by_namespace,plugins}`. 22 | 23 | ## v0.2 (16th Jun 2018) 24 | - Added plugin support! That includes: 25 | - `plugins::{Metadata,Filter,FilterFunction}` types and traits for making plugins; 26 | - `export_vapoursynth_plugin!` macro for exporting a VapourSynth plugin; 27 | - `make_filter_function!` macro for making filters without much boilerplate. 28 | - Added a sample plugin in the `sample-filter` folder. 29 | - Added the `component::Component` trait and `Frame::plane*()` accessors for safely working with the pixel data without having to manually transmute slices, including an optional half-precision float support using the `half` crate. 30 | - Added `plugin::Plugin` and other relevant APIs for enumerating plugins and invoking their functions. 31 | - Added lifetime parameters to many types to fix soundness issues. 32 | - Split `Frame` into `Frame`, `FrameRef`, `FrameRefMut`. 33 | - Added the `map::Value` trait and generic `Map::{get,get_iter,set,append}()` functions. 34 | - Added format caching in `Frame` to reduce the number of API calls needed. 35 | - Added some convenience `From` impls. 36 | 37 | ### v0.1.2 (2nd Apr 2018) 38 | - Fixed `Frame::data_row()` returning slices of incorrect rows (using the `plane` value instead of the `row` value). 39 | 40 | ### v0.1.1 (24th Mar 2018) 41 | - Added support for targetting 32-bit Windows 42 | - Added automatic detection of common Windows VapourSynth library dirs 43 | - Fixed `Frame::data()` and `Frame::data_row()` returning slices of incorrect sizes (too short) for pixel formats with more than 1 byte per pixel 44 | 45 | ## v0.1.0 46 | - Initial release 47 | -------------------------------------------------------------------------------- /vapoursynth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vapoursynth" 3 | edition = "2021" 4 | version = "0.4.0" # remember to update html_root_url 5 | authors = ["Ivan Molodetskikh "] 6 | description = "Safe Rust wrapper for VapourSynth and VSScript." 7 | license = "MIT/Apache-2.0" 8 | 9 | readme = "README.md" 10 | documentation = "https://docs.rs/vapoursynth" 11 | repository = "https://github.com/YaLTeR/vapoursynth-rs" 12 | keywords = ["vapoursynth", "vsscript", "video", "bindings"] 13 | categories = ["api-bindings", "external-ffi-bindings", "multimedia::video"] 14 | 15 | [badges] 16 | travis-ci = { repository = "YaLTeR/vapoursynth-rs" } 17 | appveyor = { repository = "YaLTeR/vapoursynth-rs" } 18 | 19 | [dependencies] 20 | bitflags = "1.3.2" 21 | half = { version = "2.0.0", optional = true } 22 | anyhow = "1.0.58" 23 | thiserror = "1.0.31" 24 | lazy_static = "1.4.0" 25 | vapoursynth-sys = { version = "0.4", path = "../vapoursynth-sys" } 26 | 27 | [dev-dependencies] 28 | clap = "3.2.10" 29 | lazy_static = "1.4.0" 30 | 31 | [dev-dependencies.num-rational] 32 | version = "0.4.1" 33 | default-features = false 34 | features = ["std"] 35 | 36 | [features] 37 | # Enable the half::f16 type to be used for frame pixel data. 38 | f16-pixel-type = ["half"] 39 | 40 | # Features for enabling higher API versions. 41 | vapoursynth-api-31 = [ 42 | "vapoursynth-sys/vapoursynth-api-31", 43 | "gte-vapoursynth-api-31", 44 | ] 45 | vapoursynth-api-32 = [ 46 | "vapoursynth-sys/vapoursynth-api-32", 47 | "gte-vapoursynth-api-31", 48 | "gte-vapoursynth-api-32", 49 | ] 50 | vapoursynth-api-33 = [ 51 | "vapoursynth-sys/vapoursynth-api-33", 52 | "gte-vapoursynth-api-31", 53 | "gte-vapoursynth-api-32", 54 | "gte-vapoursynth-api-33", 55 | ] 56 | vapoursynth-api-34 = [ 57 | "vapoursynth-sys/vapoursynth-api-34", 58 | "gte-vapoursynth-api-31", 59 | "gte-vapoursynth-api-32", 60 | "gte-vapoursynth-api-33", 61 | "gte-vapoursynth-api-34", 62 | ] 63 | vapoursynth-api-35 = [ 64 | "vapoursynth-sys/vapoursynth-api-35", 65 | "gte-vapoursynth-api-31", 66 | "gte-vapoursynth-api-32", 67 | "gte-vapoursynth-api-33", 68 | "gte-vapoursynth-api-34", 69 | "gte-vapoursynth-api-35", 70 | ] 71 | vapoursynth-api-36 = [ 72 | "vapoursynth-sys/vapoursynth-api-36", 73 | "gte-vapoursynth-api-31", 74 | "gte-vapoursynth-api-32", 75 | "gte-vapoursynth-api-33", 76 | "gte-vapoursynth-api-34", 77 | "gte-vapoursynth-api-35", 78 | "gte-vapoursynth-api-36", 79 | ] 80 | 81 | vsscript-api-31 = ["vapoursynth-sys/vsscript-api-31", "gte-vsscript-api-31"] 82 | vsscript-api-32 = [ 83 | "vapoursynth-sys/vsscript-api-32", 84 | "gte-vsscript-api-31", 85 | "gte-vsscript-api-32", 86 | ] 87 | 88 | # Features for linking to certain functions. 89 | vapoursynth-functions = ["vapoursynth-sys/vapoursynth-functions"] 90 | vsscript-functions = ["vapoursynth-sys/vsscript-functions"] 91 | 92 | # Utility features, not for outside use. 93 | gte-vapoursynth-api-31 = [] 94 | gte-vapoursynth-api-32 = [] 95 | gte-vapoursynth-api-33 = [] 96 | gte-vapoursynth-api-34 = [] 97 | gte-vapoursynth-api-35 = [] 98 | gte-vapoursynth-api-36 = [] 99 | 100 | gte-vsscript-api-31 = [] 101 | gte-vsscript-api-32 = [] 102 | 103 | # For development. 104 | #default = ["vapoursynth-api-36", "vsscript-api-32", "vapoursynth-functions", "vsscript-functions"] 105 | 106 | [package.metadata.docs.rs] 107 | features = [ 108 | "vapoursynth-api-36", 109 | "vsscript-api-32", 110 | "vapoursynth-functions", 111 | "vsscript-functions", 112 | "f16-pixel-type", 113 | ] 114 | -------------------------------------------------------------------------------- /vapoursynth/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /vapoursynth/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /vapoursynth/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /vapoursynth/examples/vpy-info.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | #[macro_use] 3 | extern crate vapoursynth; 4 | 5 | use anyhow::{anyhow, bail, Context, Error}; 6 | 7 | use std::env; 8 | use vapoursynth::prelude::*; 9 | 10 | fn usage() { 11 | println!( 12 | "Usage:\n\t{} [frame number]", 13 | env::current_exe() 14 | .ok() 15 | .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned())) 16 | .unwrap_or_else(|| "vpy-info".to_owned()) 17 | ); 18 | } 19 | 20 | #[cfg(all( 21 | feature = "vsscript-functions", 22 | any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32") 23 | ))] 24 | fn print_node_info(node: &Node) { 25 | use std::fmt::Debug; 26 | 27 | // Helper function for printing properties. 28 | fn map_or_variable(x: &Property, f: F) -> String 29 | where 30 | T: Debug + Clone + Copy + Eq + PartialEq, 31 | F: FnOnce(&T) -> String, 32 | { 33 | match *x { 34 | Property::Variable => "variable".to_owned(), 35 | Property::Constant(ref x) => f(x), 36 | } 37 | } 38 | 39 | let info = node.info(); 40 | 41 | println!( 42 | "Format: {}", 43 | map_or_variable(&info.format, |x| x.name().to_owned()) 44 | ); 45 | println!( 46 | "Resolution: {}", 47 | map_or_variable(&info.resolution, |x| format!("{}×{}", x.width, x.height)) 48 | ); 49 | println!( 50 | "Framerate: {}", 51 | map_or_variable(&info.framerate, |x| format!( 52 | "{}/{} ({})", 53 | x.numerator, 54 | x.denominator, 55 | x.numerator as f64 / x.denominator as f64 56 | )) 57 | ); 58 | 59 | #[cfg(feature = "gte-vapoursynth-api-32")] 60 | println!("Frame count: {}", info.num_frames); 61 | 62 | #[cfg(not(feature = "gte-vapoursynth-api-32"))] 63 | println!( 64 | "Frame count: {}", 65 | map_or_variable(&info.num_frames, |x| format!("{}", x)) 66 | ); 67 | } 68 | 69 | #[cfg(all( 70 | feature = "vsscript-functions", 71 | any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32") 72 | ))] 73 | fn run() -> Result<(), Error> { 74 | let filename = env::args() 75 | .nth(1) 76 | .ok_or_else(|| anyhow!("The filename argument is missing"))?; 77 | let environment = 78 | vsscript::Environment::from_file(filename, vsscript::EvalFlags::SetWorkingDir) 79 | .context("Couldn't create the VSScript environment")?; 80 | 81 | let core = environment 82 | .get_core() 83 | .context("Couldn't get the VapourSynth core")?; 84 | println!("{}", core.info()); 85 | 86 | #[cfg(feature = "gte-vsscript-api-31")] 87 | let (node, alpha_node) = environment 88 | .get_output(0) 89 | .context("Couldn't get the output at index 0")?; 90 | #[cfg(not(feature = "gte-vsscript-api-31"))] 91 | let (node, alpha_node) = ( 92 | environment 93 | .get_output(0) 94 | .context("Couldn't get the output at index 0")?, 95 | None::, 96 | ); 97 | 98 | print_node_info(&node); 99 | 100 | println!(); 101 | if let Some(alpha_node) = alpha_node { 102 | println!("Alpha:"); 103 | print_node_info(&alpha_node); 104 | } else { 105 | println!("Alpha: No"); 106 | } 107 | 108 | if let Some(n) = env::args().nth(2) { 109 | let n = n 110 | .parse::() 111 | .context("Couldn't parse the frame number")?; 112 | if n > i32::MAX as usize { 113 | bail!("Frame number is too big"); 114 | } 115 | 116 | let frame = node.get_frame(n).context("Couldn't get the frame")?; 117 | 118 | println!(); 119 | println!("Frame #{}", n); 120 | 121 | let format = frame.format(); 122 | println!("Format: {}", format.name()); 123 | println!("Plane count: {}", format.plane_count()); 124 | 125 | let props = frame.props(); 126 | let count = props.key_count(); 127 | 128 | if count > 0 { 129 | println!(); 130 | } 131 | 132 | for k in 0..count { 133 | let key = props.key(k); 134 | 135 | macro_rules! print_value { 136 | ($func:ident) => { 137 | println!( 138 | "Property: {} => {:?}", 139 | key, 140 | props.$func(key).unwrap().collect::>() 141 | ) 142 | }; 143 | } 144 | 145 | match props.value_type(key).unwrap() { 146 | ValueType::Int => print_value!(get_int_iter), 147 | ValueType::Float => print_value!(get_float_iter), 148 | ValueType::Data => print_value!(get_data_iter), 149 | ValueType::Node => print_value!(get_node_iter), 150 | ValueType::Frame => print_value!(get_frame_iter), 151 | ValueType::Function => print_value!(get_function_iter), 152 | } 153 | } 154 | 155 | for plane in 0..format.plane_count() { 156 | println!(); 157 | println!("Plane #{}", plane); 158 | println!("Resolution: {}×{}", frame.width(plane), frame.height(plane)); 159 | println!("Stride: {}", frame.stride(plane)); 160 | } 161 | } 162 | 163 | Ok(()) 164 | } 165 | 166 | #[cfg(not(all( 167 | feature = "vsscript-functions", 168 | any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32") 169 | )))] 170 | fn run() -> Result<(), Error> { 171 | bail!( 172 | "This example requires the `vsscript-functions` and either `vapoursynth-functions` or \ 173 | `vsscript-api-32` features." 174 | ) 175 | } 176 | 177 | fn main() -> anyhow::Result<()> { 178 | run() 179 | } 180 | -------------------------------------------------------------------------------- /vapoursynth/src/component.rs: -------------------------------------------------------------------------------- 1 | //! The pixel component trait. 2 | 3 | #[cfg(feature = "f16-pixel-type")] 4 | use half::f16; 5 | 6 | use crate::format::{Format, SampleType}; 7 | 8 | /// A trait for possible pixel components. 9 | /// 10 | /// # Safety 11 | /// Implementing this trait allows retrieving slices of pixel data from the frame for the target 12 | /// type, so the target type must be valid for the given format. 13 | pub unsafe trait Component { 14 | /// Returns whether this component is valid for this format. 15 | fn is_valid(format: Format) -> bool; 16 | } 17 | 18 | unsafe impl Component for u8 { 19 | #[inline] 20 | fn is_valid(format: Format) -> bool { 21 | format.sample_type() == SampleType::Integer && format.bytes_per_sample() == 1 22 | } 23 | } 24 | 25 | unsafe impl Component for u16 { 26 | #[inline] 27 | fn is_valid(format: Format) -> bool { 28 | format.sample_type() == SampleType::Integer && format.bytes_per_sample() == 2 29 | } 30 | } 31 | 32 | unsafe impl Component for u32 { 33 | #[inline] 34 | fn is_valid(format: Format) -> bool { 35 | format.sample_type() == SampleType::Integer && format.bytes_per_sample() == 4 36 | } 37 | } 38 | 39 | #[cfg(feature = "f16-pixel-type")] 40 | unsafe impl Component for f16 { 41 | #[inline] 42 | fn is_valid(format: Format) -> bool { 43 | format.sample_type() == SampleType::Float && format.bytes_per_sample() == 2 44 | } 45 | } 46 | 47 | unsafe impl Component for f32 { 48 | #[inline] 49 | fn is_valid(format: Format) -> bool { 50 | format.sample_type() == SampleType::Float && format.bytes_per_sample() == 4 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vapoursynth/src/core.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth cores. 2 | 3 | use std::ffi::{CStr, CString, NulError}; 4 | use std::fmt; 5 | use std::marker::PhantomData; 6 | use std::ptr::NonNull; 7 | use vapoursynth_sys as ffi; 8 | 9 | use crate::api::API; 10 | use crate::format::{ColorFamily, Format, FormatID, SampleType}; 11 | use crate::map::OwnedMap; 12 | use crate::plugin::Plugin; 13 | 14 | /// Contains information about a VapourSynth core. 15 | #[derive(Debug, Clone, Copy, Hash)] 16 | pub struct Info { 17 | /// String containing the name of the library, copyright notice, core and API versions. 18 | pub version_string: &'static str, 19 | 20 | /// Version of the core. 21 | pub core_version: i32, 22 | 23 | /// Version of the API. 24 | pub api_version: i32, 25 | 26 | /// Number of worker threads. 27 | pub num_threads: usize, 28 | 29 | /// The framebuffer cache will be allowed to grow up to this size (bytes) before memory is 30 | /// aggressively reclaimed. 31 | pub max_framebuffer_size: u64, 32 | 33 | /// Current size of the framebuffer cache, in bytes. 34 | pub used_framebuffer_size: u64, 35 | } 36 | 37 | /// A reference to a VapourSynth core. 38 | #[derive(Debug, Clone, Copy)] 39 | pub struct CoreRef<'core> { 40 | handle: NonNull, 41 | _owner: PhantomData<&'core ()>, 42 | } 43 | 44 | unsafe impl<'core> Send for CoreRef<'core> {} 45 | unsafe impl<'core> Sync for CoreRef<'core> {} 46 | 47 | impl<'core> CoreRef<'core> { 48 | /// Wraps `handle` in a `CoreRef`. 49 | /// 50 | /// # Safety 51 | /// The caller must ensure `handle` is valid and API is cached. 52 | #[inline] 53 | pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSCore) -> Self { 54 | Self { 55 | handle: NonNull::new_unchecked(handle), 56 | _owner: PhantomData, 57 | } 58 | } 59 | 60 | /// Returns the underlying pointer. 61 | #[inline] 62 | pub(crate) fn ptr(&self) -> *mut ffi::VSCore { 63 | self.handle.as_ptr() 64 | } 65 | 66 | /// Returns information about the VapourSynth core. 67 | pub fn info(self) -> Info { 68 | #[cfg(not(feature = "gte-vapoursynth-api-36"))] 69 | let raw_info = unsafe { 70 | API::get_cached() 71 | .get_core_info(self.handle.as_ptr()) 72 | .as_ref() 73 | .unwrap() 74 | }; 75 | #[cfg(feature = "gte-vapoursynth-api-36")] 76 | let raw_info = unsafe { &API::get_cached().get_core_info(self.handle.as_ptr()) }; 77 | 78 | let version_string = unsafe { CStr::from_ptr(raw_info.versionString).to_str().unwrap() }; 79 | debug_assert!(raw_info.numThreads >= 0); 80 | debug_assert!(raw_info.maxFramebufferSize >= 0); 81 | debug_assert!(raw_info.usedFramebufferSize >= 0); 82 | 83 | Info { 84 | version_string, 85 | core_version: raw_info.core, 86 | api_version: raw_info.api, 87 | num_threads: raw_info.numThreads as usize, 88 | max_framebuffer_size: raw_info.maxFramebufferSize as u64, 89 | used_framebuffer_size: raw_info.usedFramebufferSize as u64, 90 | } 91 | } 92 | 93 | /// Retrieves a registered or preset `Format` by its id. The id can be of a previously 94 | /// registered format, or one of the `PresetFormat`. 95 | #[inline] 96 | pub fn get_format(&self, id: FormatID) -> Option> { 97 | let ptr = unsafe { API::get_cached().get_format_preset(id.0, self.handle.as_ptr()) }; 98 | unsafe { ptr.as_ref().map(|p| Format::from_ptr(p)) } 99 | } 100 | 101 | /// Registers a custom video format. 102 | /// 103 | /// Returns `None` if an invalid format is described. 104 | /// 105 | /// Registering compat formats is not allowed. Only certain privileged built-in filters are 106 | /// allowed to handle compat formats. 107 | /// 108 | /// RGB formats are not allowed to be subsampled. 109 | #[inline] 110 | pub fn register_format( 111 | &self, 112 | color_family: ColorFamily, 113 | sample_type: SampleType, 114 | bits_per_sample: u8, 115 | sub_sampling_w: u8, 116 | sub_sampling_h: u8, 117 | ) -> Option> { 118 | unsafe { 119 | API::get_cached() 120 | .register_format( 121 | color_family.into(), 122 | sample_type.into(), 123 | i32::from(bits_per_sample), 124 | i32::from(sub_sampling_w), 125 | i32::from(sub_sampling_h), 126 | self.handle.as_ptr(), 127 | ) 128 | .as_ref() 129 | .map(|p| Format::from_ptr(p)) 130 | } 131 | } 132 | 133 | /// Returns a plugin with the given identifier. 134 | #[inline] 135 | pub fn get_plugin_by_id(&self, id: &str) -> Result>, NulError> { 136 | let id = CString::new(id)?; 137 | let ptr = unsafe { API::get_cached().get_plugin_by_id(id.as_ptr(), self.handle.as_ptr()) }; 138 | if ptr.is_null() { 139 | Ok(None) 140 | } else { 141 | Ok(Some(unsafe { Plugin::from_ptr(ptr) })) 142 | } 143 | } 144 | 145 | /// Returns a plugin with the given namespace. 146 | /// 147 | /// `get_plugin_by_id()` should be used instead. 148 | #[inline] 149 | pub fn get_plugin_by_namespace( 150 | &self, 151 | namespace: &str, 152 | ) -> Result>, NulError> { 153 | let namespace = CString::new(namespace)?; 154 | let ptr = 155 | unsafe { API::get_cached().get_plugin_by_ns(namespace.as_ptr(), self.handle.as_ptr()) }; 156 | if ptr.is_null() { 157 | Ok(None) 158 | } else { 159 | Ok(Some(unsafe { Plugin::from_ptr(ptr) })) 160 | } 161 | } 162 | 163 | /// Returns a map containing a list of all loaded plugins. 164 | /// 165 | /// Keys: meaningless unique strings; 166 | /// 167 | /// Values: namespace, identifier, and full name, separated by semicolons. 168 | // TODO: parse the values on the crate side and return a nice struct. 169 | #[inline] 170 | pub fn plugins(&self) -> OwnedMap<'core> { 171 | unsafe { OwnedMap::from_ptr(API::get_cached().get_plugins(self.handle.as_ptr())) } 172 | } 173 | 174 | /// Sets the maximum size of the framebuffer cache. Returns the new maximum size. 175 | #[cfg(feature = "gte-vapoursynth-api-36")] 176 | #[inline] 177 | pub fn set_max_cache_size(&self, bytes: i64) -> i64 { 178 | unsafe { API::get_cached().set_max_cache_size(bytes, self.handle.as_ptr()) } 179 | } 180 | 181 | /// Sets the number of worker threads for the given core. 182 | /// 183 | /// If the requested number of threads is zero or lower, the number of hardware threads will be 184 | /// detected and used. 185 | /// 186 | /// Returns the new thread count. 187 | #[cfg(feature = "gte-vapoursynth-api-36")] 188 | #[inline] 189 | pub fn set_thread_count(&self, threads: i32) -> i32 { 190 | unsafe { API::get_cached().set_thread_count(threads, self.handle.as_ptr()) } 191 | } 192 | } 193 | 194 | impl fmt::Display for Info { 195 | #[inline] 196 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 197 | write!(f, "{}", self.version_string)?; 198 | writeln!(f, "Worker threads: {}", self.num_threads)?; 199 | writeln!( 200 | f, 201 | "Max framebuffer cache size: {}", 202 | self.max_framebuffer_size 203 | )?; 204 | writeln!( 205 | f, 206 | "Current framebuffer cache size: {}", 207 | self.used_framebuffer_size 208 | ) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /vapoursynth/src/format.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth frame formats. 2 | 3 | use std::ffi::CStr; 4 | use std::fmt::{self, Display}; 5 | use std::marker::PhantomData; 6 | use std::ops::Deref; 7 | use vapoursynth_sys as ffi; 8 | 9 | /// Contains information about a video format. 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct Format<'core> { 12 | handle: &'core ffi::VSFormat, 13 | } 14 | 15 | /// Preset VapourSynth formats. 16 | /// 17 | /// The presets suffixed with H and S have floating point sample type. The H and S suffixes stand 18 | /// for half precision and single precision, respectively. 19 | /// 20 | /// The compat formats are the only packed formats in VapourSynth. Everything else is planar. They 21 | /// exist for compatibility with Avisynth plugins. They are not to be implemented in native 22 | /// VapourSynth plugins. 23 | #[allow(clippy::unreadable_literal)] 24 | #[repr(i32)] 25 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 26 | pub enum PresetFormat { 27 | Gray8 = 1000010, 28 | Gray16 = 1000011, 29 | GrayH = 1000012, 30 | GrayS = 1000013, 31 | YUV420P8 = 3000010, 32 | YUV422P8 = 3000011, 33 | YUV444P8 = 3000012, 34 | YUV410P8 = 3000013, 35 | YUV411P8 = 3000014, 36 | YUV440P8 = 3000015, 37 | YUV420P9 = 3000016, 38 | YUV422P9 = 3000017, 39 | YUV444P9 = 3000018, 40 | YUV420P10 = 3000019, 41 | YUV422P10 = 3000020, 42 | YUV444P10 = 3000021, 43 | YUV420P16 = 3000022, 44 | YUV422P16 = 3000023, 45 | YUV444P16 = 3000024, 46 | YUV444PH = 3000025, 47 | YUV444PS = 3000026, 48 | YUV420P12 = 3000027, 49 | YUV422P12 = 3000028, 50 | YUV444P12 = 3000029, 51 | YUV420P14 = 3000030, 52 | YUV422P14 = 3000031, 53 | YUV444P14 = 3000032, 54 | RGB24 = 2000010, 55 | RGB27 = 2000011, 56 | RGB30 = 2000012, 57 | RGB48 = 2000013, 58 | RGBH = 2000014, 59 | RGBS = 2000015, 60 | CompatBGR32 = 9000010, 61 | CompatYUY2 = 9000011, 62 | } 63 | 64 | /// Format color families. 65 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 66 | pub enum ColorFamily { 67 | Gray = 1000000, 68 | RGB = 2000000, 69 | YUV = 3000000, 70 | YCoCg = 4000000, 71 | Compat = 9000000, 72 | } 73 | 74 | /// Format sample types. 75 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 76 | pub enum SampleType { 77 | Integer, 78 | Float, 79 | } 80 | 81 | /// A unique format identifier. 82 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 83 | pub struct FormatID(pub(crate) i32); 84 | 85 | impl<'core> PartialEq for Format<'core> { 86 | #[inline] 87 | fn eq(&self, other: &Format<'core>) -> bool { 88 | self.id() == other.id() 89 | } 90 | } 91 | 92 | impl<'core> Eq for Format<'core> {} 93 | 94 | #[doc(hidden)] 95 | impl<'core> Deref for Format<'core> { 96 | type Target = ffi::VSFormat; 97 | 98 | // Technically this should return `&'core`. 99 | #[inline] 100 | fn deref(&self) -> &Self::Target { 101 | self.handle 102 | } 103 | } 104 | 105 | impl<'core> Format<'core> { 106 | /// Wraps a raw pointer in a `Format`. 107 | /// 108 | /// # Safety 109 | /// The caller must ensure `ptr` and the lifetime is valid. 110 | #[inline] 111 | pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSFormat) -> Self { 112 | Self { handle: &*ptr } 113 | } 114 | 115 | /// Gets the unique identifier of this format. 116 | #[inline] 117 | pub fn id(self) -> FormatID { 118 | FormatID(self.handle.id) 119 | } 120 | 121 | /// Gets the printable name of this format. 122 | #[inline] 123 | pub fn name(self) -> &'core str { 124 | unsafe { CStr::from_ptr(&self.handle.name as _).to_str().unwrap() } 125 | } 126 | 127 | /// Gets the number of planes of this format. 128 | #[inline] 129 | pub fn plane_count(self) -> usize { 130 | let plane_count = self.handle.numPlanes; 131 | debug_assert!(plane_count >= 0); 132 | plane_count as usize 133 | } 134 | 135 | /// Gets the color family of this format. 136 | #[inline] 137 | pub fn color_family(self) -> ColorFamily { 138 | match self.handle.colorFamily { 139 | x if x == ffi::VSColorFamily::cmGray as i32 => ColorFamily::Gray, 140 | x if x == ffi::VSColorFamily::cmRGB as i32 => ColorFamily::RGB, 141 | x if x == ffi::VSColorFamily::cmYUV as i32 => ColorFamily::YUV, 142 | x if x == ffi::VSColorFamily::cmYCoCg as i32 => ColorFamily::YCoCg, 143 | x if x == ffi::VSColorFamily::cmCompat as i32 => ColorFamily::Compat, 144 | _ => unreachable!(), 145 | } 146 | } 147 | 148 | /// Gets the sample type of this format. 149 | #[inline] 150 | pub fn sample_type(self) -> SampleType { 151 | match self.handle.sampleType { 152 | x if x == ffi::VSSampleType::stInteger as i32 => SampleType::Integer, 153 | x if x == ffi::VSSampleType::stFloat as i32 => SampleType::Float, 154 | _ => unreachable!(), 155 | } 156 | } 157 | 158 | /// Gets the number of significant bits per sample. 159 | #[inline] 160 | pub fn bits_per_sample(self) -> u8 { 161 | let rv = self.handle.bitsPerSample; 162 | debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX)); 163 | rv as u8 164 | } 165 | 166 | /// Gets the number of bytes needed for a sample. This is always a power of 2 and the smallest 167 | /// possible that can fit the number of bits used per sample. 168 | #[inline] 169 | pub fn bytes_per_sample(self) -> u8 { 170 | let rv = self.handle.bytesPerSample; 171 | debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX)); 172 | rv as u8 173 | } 174 | 175 | /// log2 subsampling factor, applied to second and third plane. 176 | #[inline] 177 | pub fn sub_sampling_w(self) -> u8 { 178 | let rv = self.handle.subSamplingW; 179 | debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX)); 180 | rv as u8 181 | } 182 | 183 | /// log2 subsampling factor, applied to second and third plane. 184 | #[inline] 185 | pub fn sub_sampling_h(self) -> u8 { 186 | let rv = self.handle.subSamplingH; 187 | debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX)); 188 | rv as u8 189 | } 190 | } 191 | 192 | impl From for FormatID { 193 | fn from(x: PresetFormat) -> Self { 194 | FormatID(x as i32) 195 | } 196 | } 197 | 198 | #[doc(hidden)] 199 | impl From for ffi::VSColorFamily { 200 | #[inline] 201 | fn from(x: ColorFamily) -> Self { 202 | match x { 203 | ColorFamily::Gray => ffi::VSColorFamily::cmGray, 204 | ColorFamily::RGB => ffi::VSColorFamily::cmRGB, 205 | ColorFamily::YUV => ffi::VSColorFamily::cmYUV, 206 | ColorFamily::YCoCg => ffi::VSColorFamily::cmYCoCg, 207 | ColorFamily::Compat => ffi::VSColorFamily::cmCompat, 208 | } 209 | } 210 | } 211 | 212 | #[doc(hidden)] 213 | impl From for ffi::VSSampleType { 214 | #[inline] 215 | fn from(x: SampleType) -> Self { 216 | match x { 217 | SampleType::Integer => ffi::VSSampleType::stInteger, 218 | SampleType::Float => ffi::VSSampleType::stFloat, 219 | } 220 | } 221 | } 222 | 223 | impl Display for ColorFamily { 224 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 225 | write!( 226 | f, 227 | "{}", 228 | match *self { 229 | ColorFamily::Gray => "Gray", 230 | ColorFamily::RGB => "RGB", 231 | ColorFamily::YUV => "YUV", 232 | ColorFamily::YCoCg => "YCoCg", 233 | ColorFamily::Compat => "Compat", 234 | } 235 | ) 236 | } 237 | } 238 | 239 | impl Display for SampleType { 240 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 241 | write!( 242 | f, 243 | "{}", 244 | match *self { 245 | SampleType::Integer => "Integer", 246 | SampleType::Float => "Float", 247 | } 248 | ) 249 | } 250 | } 251 | 252 | impl From for FormatID { 253 | fn from(x: i32) -> Self { 254 | FormatID(x) 255 | } 256 | } 257 | 258 | impl From for i32 { 259 | fn from(x: FormatID) -> Self { 260 | x.0 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /vapoursynth/src/frame.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth frames. 2 | 3 | use std::marker::PhantomData; 4 | use std::ops::{Deref, DerefMut}; 5 | use std::ptr::{self, NonNull}; 6 | use std::{mem, slice}; 7 | use vapoursynth_sys as ffi; 8 | 9 | use thiserror::Error; 10 | 11 | use crate::api::API; 12 | use crate::component::Component; 13 | use crate::core::CoreRef; 14 | use crate::format::Format; 15 | use crate::map::{MapRef, MapRefMut}; 16 | use crate::video_info::Resolution; 17 | 18 | /// An error indicating that the frame data has non-zero padding. 19 | #[derive(Error, Debug, Clone, Copy, Eq, PartialEq)] 20 | #[error("Frame data has non-zero padding: {}", _0)] 21 | pub struct NonZeroPadding(usize); 22 | 23 | /// One frame of a clip. 24 | // This type is intended to be publicly used only in reference form. 25 | #[derive(Debug)] 26 | pub struct Frame<'core> { 27 | // The actual mutability of this depends on whether it's accessed via `&Frame` or `&mut Frame`. 28 | handle: NonNull, 29 | // The cached frame format for fast access. 30 | format: Format<'core>, 31 | _owner: PhantomData<&'core ()>, 32 | } 33 | 34 | /// A reference to a ref-counted frame. 35 | #[derive(Debug)] 36 | pub struct FrameRef<'core> { 37 | // Only immutable references to this are allowed. 38 | frame: Frame<'core>, 39 | } 40 | 41 | /// A reference to a mutable frame. 42 | #[derive(Debug)] 43 | pub struct FrameRefMut<'core> { 44 | // Both mutable and immutable references to this are allowed. 45 | frame: Frame<'core>, 46 | } 47 | 48 | unsafe impl<'core> Send for Frame<'core> {} 49 | unsafe impl<'core> Sync for Frame<'core> {} 50 | 51 | #[doc(hidden)] 52 | impl<'core> Deref for Frame<'core> { 53 | type Target = ffi::VSFrameRef; 54 | 55 | // Technically this should return `&'core`. 56 | #[inline] 57 | fn deref(&self) -> &Self::Target { 58 | unsafe { self.handle.as_ref() } 59 | } 60 | } 61 | 62 | #[doc(hidden)] 63 | impl<'core> DerefMut for Frame<'core> { 64 | // Technically this should return `&'core`. 65 | #[inline] 66 | fn deref_mut(&mut self) -> &mut Self::Target { 67 | unsafe { self.handle.as_mut() } 68 | } 69 | } 70 | 71 | impl<'core> Drop for Frame<'core> { 72 | #[inline] 73 | fn drop(&mut self) { 74 | unsafe { 75 | API::get_cached().free_frame(self); 76 | } 77 | } 78 | } 79 | 80 | impl<'core> Clone for FrameRef<'core> { 81 | #[inline] 82 | fn clone(&self) -> Self { 83 | unsafe { 84 | let handle = API::get_cached().clone_frame(self); 85 | Self { 86 | frame: Frame::from_ptr(handle), 87 | } 88 | } 89 | } 90 | } 91 | 92 | impl<'core> Deref for FrameRef<'core> { 93 | type Target = Frame<'core>; 94 | 95 | #[inline] 96 | fn deref(&self) -> &Self::Target { 97 | &self.frame 98 | } 99 | } 100 | 101 | impl<'core> Deref for FrameRefMut<'core> { 102 | type Target = Frame<'core>; 103 | 104 | #[inline] 105 | fn deref(&self) -> &Self::Target { 106 | &self.frame 107 | } 108 | } 109 | 110 | impl<'core> DerefMut for FrameRefMut<'core> { 111 | #[inline] 112 | fn deref_mut(&mut self) -> &mut Self::Target { 113 | &mut self.frame 114 | } 115 | } 116 | 117 | impl<'core> FrameRef<'core> { 118 | /// Wraps `handle` in a `FrameRef`. 119 | /// 120 | /// # Safety 121 | /// The caller must ensure `handle` and the lifetime is valid and API is cached. 122 | #[inline] 123 | pub(crate) unsafe fn from_ptr(handle: *const ffi::VSFrameRef) -> Self { 124 | Self { 125 | frame: Frame::from_ptr(handle), 126 | } 127 | } 128 | } 129 | 130 | impl<'core> FrameRefMut<'core> { 131 | /// Wraps `handle` in a `FrameRefMut`. 132 | /// 133 | /// # Safety 134 | /// The caller must ensure `handle` and the lifetime is valid and API is cached. 135 | #[inline] 136 | pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSFrameRef) -> Self { 137 | Self { 138 | frame: Frame::from_ptr(handle), 139 | } 140 | } 141 | 142 | /// Creates a copy of the given frame. 143 | /// 144 | /// The plane data is copy-on-write, so this isn't very expensive by itself. 145 | /// 146 | /// Judging by the underlying implementation, it seems that any valid `core` can be used. 147 | #[inline] 148 | pub fn copy_of(core: CoreRef, frame: &Frame<'core>) -> Self { 149 | Self { 150 | frame: unsafe { Frame::from_ptr(API::get_cached().copy_frame(frame, core.ptr())) }, 151 | } 152 | } 153 | 154 | /// Creates a new frame with uninitialized plane data. 155 | /// 156 | /// Optionally copies the frame properties from the provided `prop_src` frame. 157 | /// 158 | /// # Safety 159 | /// The returned frame contains uninitialized plane data. This should be handled carefully. See 160 | /// the docs for `std::mem::uninitialized()` for more information. 161 | /// 162 | /// # Panics 163 | /// Panics if the given resolution has components that don't fit into an `i32`. 164 | #[inline] 165 | pub unsafe fn new_uninitialized( 166 | core: CoreRef<'core>, 167 | prop_src: Option<&Frame<'core>>, 168 | format: Format<'core>, 169 | resolution: Resolution, 170 | ) -> Self { 171 | assert!(resolution.width <= i32::MAX as usize); 172 | assert!(resolution.height <= i32::MAX as usize); 173 | 174 | Self { 175 | frame: unsafe { 176 | Frame::from_ptr(API::get_cached().new_video_frame( 177 | &format, 178 | resolution.width as i32, 179 | resolution.height as i32, 180 | prop_src.map(|f| f.deref() as _).unwrap_or(ptr::null()), 181 | core.ptr(), 182 | )) 183 | }, 184 | } 185 | } 186 | } 187 | 188 | impl<'core> From> for FrameRef<'core> { 189 | #[inline] 190 | fn from(x: FrameRefMut<'core>) -> Self { 191 | Self { frame: x.frame } 192 | } 193 | } 194 | 195 | impl<'core> Frame<'core> { 196 | /// Converts a pointer to a frame to a reference. 197 | /// 198 | /// # Safety 199 | /// The caller needs to ensure the pointer and the lifetime is valid, and that the resulting 200 | /// `Frame` gets put into `FrameRef` or `FrameRefMut` according to the input pointer 201 | /// mutability. 202 | #[inline] 203 | pub(crate) unsafe fn from_ptr(handle: *const ffi::VSFrameRef) -> Self { 204 | Self { 205 | handle: NonNull::new_unchecked(handle as *mut ffi::VSFrameRef), 206 | format: unsafe { 207 | let ptr = API::get_cached().get_frame_format(&*handle); 208 | Format::from_ptr(ptr) 209 | }, 210 | _owner: PhantomData, 211 | } 212 | } 213 | 214 | /// Returns the frame format. 215 | #[inline] 216 | pub fn format(&self) -> Format<'core> { 217 | self.format 218 | } 219 | 220 | /// Returns the width of a plane, in pixels. 221 | /// 222 | /// The width depends on the plane number because of the possible chroma subsampling. 223 | /// 224 | /// # Panics 225 | /// Panics if `plane >= format().plane_count()`. 226 | #[inline] 227 | pub fn width(&self, plane: usize) -> usize { 228 | assert!(plane < self.format().plane_count()); 229 | 230 | unsafe { API::get_cached().get_frame_width(self, plane as i32) as usize } 231 | } 232 | 233 | /// Returns the height of a plane, in pixels. 234 | /// 235 | /// The height depends on the plane number because of the possible chroma subsampling. 236 | /// 237 | /// # Panics 238 | /// Panics if `plane >= format().plane_count()`. 239 | #[inline] 240 | pub fn height(&self, plane: usize) -> usize { 241 | assert!(plane < self.format().plane_count()); 242 | 243 | unsafe { API::get_cached().get_frame_height(self, plane as i32) as usize } 244 | } 245 | 246 | /// Returns the resolution of a plane. 247 | /// 248 | /// The resolution depends on the plane number because of the possible chroma subsampling. 249 | /// 250 | /// # Panics 251 | /// Panics if `plane` is invalid for this frame. 252 | #[inline] 253 | pub fn resolution(&self, plane: usize) -> Resolution { 254 | assert!(plane < self.format().plane_count()); 255 | 256 | Resolution { 257 | width: self.width(plane), 258 | height: self.height(plane), 259 | } 260 | } 261 | 262 | /// Returns the distance in bytes between two consecutive lines of a plane. 263 | /// 264 | /// # Panics 265 | /// Panics if `plane >= format().plane_count()`. 266 | #[inline] 267 | pub fn stride(&self, plane: usize) -> usize { 268 | assert!(plane < self.format().plane_count()); 269 | 270 | unsafe { API::get_cached().get_frame_stride(self, plane as i32) as usize } 271 | } 272 | 273 | /// Returns a slice of a plane's pixel row. 274 | /// 275 | /// # Panics 276 | /// Panics if the requested plane, row or component type is invalid. 277 | #[inline] 278 | pub fn plane_row(&self, plane: usize, row: usize) -> &[T] { 279 | assert!(plane < self.format().plane_count()); 280 | assert!(row < self.height(plane)); 281 | assert!(T::is_valid(self.format())); 282 | 283 | let stride = self.stride(plane); 284 | let ptr = self.data_ptr(plane); 285 | 286 | let offset = stride * row; 287 | assert!(offset <= isize::MAX as usize); 288 | let offset = offset as isize; 289 | 290 | let row_ptr = unsafe { ptr.offset(offset) }; 291 | let width = self.width(plane); 292 | 293 | unsafe { slice::from_raw_parts(row_ptr as *const T, width) } 294 | } 295 | 296 | /// Returns a mutable slice of a plane's pixel row. 297 | /// 298 | /// # Panics 299 | /// Panics if the requested plane, row or component type is invalid. 300 | #[inline] 301 | pub fn plane_row_mut(&mut self, plane: usize, row: usize) -> &mut [T] { 302 | assert!(plane < self.format().plane_count()); 303 | assert!(row < self.height(plane)); 304 | assert!(T::is_valid(self.format())); 305 | 306 | let stride = self.stride(plane); 307 | let ptr = self.data_ptr_mut(plane); 308 | 309 | let offset = stride * row; 310 | assert!(offset <= isize::MAX as usize); 311 | let offset = offset as isize; 312 | 313 | let row_ptr = unsafe { ptr.offset(offset) }; 314 | let width = self.width(plane); 315 | 316 | unsafe { slice::from_raw_parts_mut(row_ptr as *mut T, width) } 317 | } 318 | 319 | /// Returns a slice of the plane's pixels. 320 | /// 321 | /// The length of the returned slice is `height() * width()`. If the pixel data has non-zero 322 | /// padding (that is, `stride()` is larger than `width()`), an error is returned, since 323 | /// returning the data slice would open access to uninitialized bytes. 324 | /// 325 | /// # Panics 326 | /// Panics if the requested plane or component type is invalid. 327 | pub fn plane(&self, plane: usize) -> Result<&[T], NonZeroPadding> { 328 | assert!(plane < self.format().plane_count()); 329 | assert!(T::is_valid(self.format())); 330 | 331 | let stride = self.stride(plane); 332 | let width_in_bytes = self.width(plane) * usize::from(self.format().bytes_per_sample()); 333 | if stride != width_in_bytes { 334 | return Err(NonZeroPadding(stride - width_in_bytes)); 335 | } 336 | 337 | let height = self.height(plane); 338 | let length = height * self.width(plane); 339 | let ptr = self.data_ptr(plane); 340 | 341 | Ok(unsafe { slice::from_raw_parts(ptr as *const T, length) }) 342 | } 343 | 344 | /// Returns a mutable slice of the plane's pixels. 345 | /// 346 | /// The length of the returned slice is `height() * width()`. If the pixel data has non-zero 347 | /// padding (that is, `stride()` is larger than `width()`), an error is returned, since 348 | /// returning the data slice would open access to uninitialized bytes. 349 | /// 350 | /// # Panics 351 | /// Panics if the requested plane or component type is invalid. 352 | pub fn plane_mut(&mut self, plane: usize) -> Result<&mut [T], NonZeroPadding> { 353 | assert!(plane < self.format().plane_count()); 354 | assert!(T::is_valid(self.format())); 355 | 356 | let stride = self.stride(plane); 357 | let width_in_bytes = self.width(plane) * usize::from(self.format().bytes_per_sample()); 358 | if stride != width_in_bytes { 359 | return Err(NonZeroPadding(stride - width_in_bytes)); 360 | } 361 | 362 | let height = self.height(plane); 363 | let length = height * self.width(plane); 364 | let ptr = self.data_ptr_mut(plane); 365 | 366 | Ok(unsafe { slice::from_raw_parts_mut(ptr as *mut T, length) }) 367 | } 368 | 369 | /// Returns a pointer to the plane's pixels. 370 | /// 371 | /// The pointer points to an array with a length of `height() * stride()` and is valid for as 372 | /// long as the frame is alive. 373 | /// 374 | /// # Panics 375 | /// Panics if `plane >= format().plane_count()`. 376 | #[inline] 377 | pub fn data_ptr(&self, plane: usize) -> *const u8 { 378 | assert!(plane < self.format().plane_count()); 379 | 380 | unsafe { API::get_cached().get_frame_read_ptr(self, plane as i32) } 381 | } 382 | 383 | /// Returns a mutable pointer to the plane's pixels. 384 | /// 385 | /// The pointer points to an array with a length of `height() * stride()` and is valid for as 386 | /// long as the frame is alive. 387 | /// 388 | /// # Panics 389 | /// Panics if `plane >= format().plane_count()`. 390 | #[inline] 391 | pub fn data_ptr_mut(&mut self, plane: usize) -> *mut u8 { 392 | assert!(plane < self.format().plane_count()); 393 | 394 | unsafe { API::get_cached().get_frame_write_ptr(self, plane as i32) } 395 | } 396 | 397 | /// Returns a slice of a plane's pixel row. 398 | /// 399 | /// The length of the returned slice is equal to `width() * format().bytes_per_sample()`. 400 | /// 401 | /// # Panics 402 | /// Panics if `plane >= format().plane_count()` or if `row >= height()`. 403 | pub fn data_row(&self, plane: usize, row: usize) -> &[u8] { 404 | assert!(plane < self.format().plane_count()); 405 | assert!(row < self.height(plane)); 406 | 407 | let stride = self.stride(plane); 408 | let ptr = self.data_ptr(plane); 409 | 410 | let offset = stride * row; 411 | assert!(offset <= isize::MAX as usize); 412 | let offset = offset as isize; 413 | 414 | let row_ptr = unsafe { ptr.offset(offset) }; 415 | let width = self.width(plane) * usize::from(self.format().bytes_per_sample()); 416 | 417 | unsafe { slice::from_raw_parts(row_ptr, width) } 418 | } 419 | 420 | /// Returns a mutable slice of a plane's pixel row. 421 | /// 422 | /// The length of the returned slice is equal to `width() * format().bytes_per_sample()`. 423 | /// 424 | /// # Panics 425 | /// Panics if `plane >= format().plane_count()` or if `row >= height()`. 426 | pub fn data_row_mut(&mut self, plane: usize, row: usize) -> &mut [u8] { 427 | assert!(plane < self.format().plane_count()); 428 | assert!(row < self.height(plane)); 429 | 430 | let stride = self.stride(plane); 431 | let ptr = self.data_ptr_mut(plane); 432 | 433 | let offset = stride * row; 434 | assert!(offset <= isize::MAX as usize); 435 | let offset = offset as isize; 436 | 437 | let row_ptr = unsafe { ptr.offset(offset) }; 438 | let width = self.width(plane) * usize::from(self.format().bytes_per_sample()); 439 | 440 | unsafe { slice::from_raw_parts_mut(row_ptr, width) } 441 | } 442 | 443 | /// Returns a slice of the plane's pixels. 444 | /// 445 | /// The length of the returned slice is `height() * width() * format().bytes_per_sample()`. If 446 | /// the pixel data has non-zero padding (that is, `stride()` is larger than `width()`), an 447 | /// error is returned, since returning the data slice would open access to uninitialized bytes. 448 | /// 449 | /// # Panics 450 | /// Panics if `plane >= format().plane_count()` or if `row >= height()`. 451 | pub fn data(&self, plane: usize) -> Result<&[u8], NonZeroPadding> { 452 | assert!(plane < self.format().plane_count()); 453 | 454 | let stride = self.stride(plane); 455 | let width = self.width(plane) * usize::from(self.format().bytes_per_sample()); 456 | if stride != width { 457 | return Err(NonZeroPadding(stride - width)); 458 | } 459 | 460 | let height = self.height(plane); 461 | let length = height * stride; 462 | let ptr = self.data_ptr(plane); 463 | 464 | Ok(unsafe { slice::from_raw_parts(ptr, length) }) 465 | } 466 | 467 | /// Returns a mutable slice of the plane's pixels. 468 | /// 469 | /// The length of the returned slice is `height() * width() * format().bytes_per_sample()`. If 470 | /// the pixel data has non-zero padding (that is, `stride()` is larger than `width()`), an 471 | /// error is returned, since returning the data slice would open access to uninitialized bytes. 472 | /// 473 | /// # Panics 474 | /// Panics if `plane >= format().plane_count()` or if `row >= height()`. 475 | pub fn data_mut(&mut self, plane: usize) -> Result<&mut [u8], NonZeroPadding> { 476 | assert!(plane < self.format().plane_count()); 477 | 478 | let stride = self.stride(plane); 479 | let width = self.width(plane) * usize::from(self.format().bytes_per_sample()); 480 | if stride != width { 481 | return Err(NonZeroPadding(stride - width)); 482 | } 483 | 484 | let height = self.height(plane); 485 | let length = height * stride; 486 | let ptr = self.data_ptr_mut(plane); 487 | 488 | Ok(unsafe { slice::from_raw_parts_mut(ptr, length) }) 489 | } 490 | 491 | /// Returns a map of frame's properties. 492 | #[inline] 493 | pub fn props(&self) -> MapRef { 494 | unsafe { MapRef::from_ptr(API::get_cached().get_frame_props_ro(self)) } 495 | } 496 | 497 | /// Returns a mutable map of frame's properties. 498 | #[inline] 499 | pub fn props_mut(&mut self) -> MapRefMut { 500 | unsafe { MapRefMut::from_ptr(API::get_cached().get_frame_props_rw(self)) } 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /vapoursynth/src/function.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth callable functions. 2 | 3 | use std::marker::PhantomData; 4 | use std::ops::{Deref, DerefMut}; 5 | use std::os::raw::c_void; 6 | use std::ptr::NonNull; 7 | use std::{mem, panic, process}; 8 | use vapoursynth_sys as ffi; 9 | 10 | use crate::api::API; 11 | use crate::core::CoreRef; 12 | use crate::map::{Map, MapRef, MapRefMut}; 13 | 14 | /// Holds a reference to a function that may be called. 15 | #[derive(Debug)] 16 | pub struct Function<'core> { 17 | handle: NonNull, 18 | _owner: PhantomData<&'core ()>, 19 | } 20 | 21 | unsafe impl<'core> Send for Function<'core> {} 22 | unsafe impl<'core> Sync for Function<'core> {} 23 | 24 | impl<'core> Drop for Function<'core> { 25 | #[inline] 26 | fn drop(&mut self) { 27 | unsafe { 28 | API::get_cached().free_func(self.handle.as_ptr()); 29 | } 30 | } 31 | } 32 | 33 | impl<'core> Clone for Function<'core> { 34 | #[inline] 35 | fn clone(&self) -> Self { 36 | let handle = unsafe { API::get_cached().clone_func(self.handle.as_ptr()) }; 37 | Self { 38 | handle: unsafe { NonNull::new_unchecked(handle) }, 39 | _owner: PhantomData, 40 | } 41 | } 42 | } 43 | 44 | impl<'core> Function<'core> { 45 | /// Wraps `handle` in a `Function`. 46 | /// 47 | /// # Safety 48 | /// The caller must ensure `handle` and the lifetime are valid and API is cached. 49 | #[inline] 50 | pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSFuncRef) -> Self { 51 | Self { 52 | handle: NonNull::new_unchecked(handle), 53 | _owner: PhantomData, 54 | } 55 | } 56 | 57 | /// Returns the underlying pointer. 58 | #[inline] 59 | pub(crate) fn ptr(&self) -> *mut ffi::VSFuncRef { 60 | self.handle.as_ptr() 61 | } 62 | 63 | /// Creates a new function. 64 | /// 65 | /// To indicate an error from the callback, set an error on the output map. 66 | #[inline] 67 | pub fn new(api: API, core: CoreRef<'core>, callback: F) -> Self 68 | where 69 | F: Fn(API, CoreRef<'core>, &Map<'core>, &mut Map<'core>) + Send + Sync + 'core, 70 | { 71 | unsafe extern "system" fn c_callback<'core, F>( 72 | in_: *const ffi::VSMap, 73 | out: *mut ffi::VSMap, 74 | user_data: *mut c_void, 75 | core: *mut ffi::VSCore, 76 | _vsapi: *const ffi::VSAPI, 77 | ) where 78 | F: Fn(API, CoreRef<'core>, &Map<'core>, &mut Map<'core>) + Send + Sync + 'core, 79 | { 80 | let closure = move || { 81 | let api = API::get_cached(); 82 | let core = CoreRef::from_ptr(core); 83 | let in_ = MapRef::from_ptr(in_); 84 | let mut out = MapRefMut::from_ptr(out); 85 | let callback = Box::from_raw(user_data as *mut F); 86 | 87 | callback(api, core, &in_, &mut out); 88 | 89 | mem::forget(callback); 90 | }; 91 | 92 | if panic::catch_unwind(closure).is_err() { 93 | process::abort(); 94 | } 95 | } 96 | 97 | unsafe extern "system" fn c_free(user_data: *mut c_void) { 98 | drop(Box::from_raw(user_data as *mut F)) 99 | } 100 | 101 | let data = Box::new(callback); 102 | 103 | let handle = unsafe { 104 | API::get_cached().create_func( 105 | c_callback::<'core, F>, 106 | Box::into_raw(data) as _, 107 | Some(c_free::), 108 | core.ptr(), 109 | ) 110 | }; 111 | 112 | Self { 113 | handle: unsafe { NonNull::new_unchecked(handle) }, 114 | _owner: PhantomData, 115 | } 116 | } 117 | 118 | /// Calls the function. If the call fails `out` will have an error set. 119 | #[inline] 120 | pub fn call(&self, in_: &Map<'core>, out: &mut Map<'core>) { 121 | unsafe { API::get_cached().call_func(self.handle.as_ptr(), in_.deref(), out.deref_mut()) }; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /vapoursynth/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A safe wrapper for [VapourSynth](https://github.com/vapoursynth/vapoursynth), written in Rust. 2 | //! 3 | //! The primary goal is safety (that is, safe Rust code should not trigger undefined behavior), and 4 | //! secondary goals include performance and ease of use. 5 | //! 6 | //! ## Functionality 7 | //! 8 | //! Most of the VapourSynth API is covered. It's possible to evaluate `.vpy` scripts, access their 9 | //! properties and output, retrieve frames; enumerate loaded plugins and invoke their functions as 10 | //! well as create VapourSynth filters. 11 | //! 12 | //! For an example usage see 13 | //! [examples/vspipe.rs](https://github.com/YaLTeR/vapoursynth-rs/blob/master/vapoursynth/examples/vspipe.rs), 14 | //! a complete reimplementation of VapourSynth's 15 | //! [vspipe](https://github.com/vapoursynth/vapoursynth/blob/master/src/vspipe/vspipe.cpp) in safe 16 | //! Rust utilizing this crate. 17 | //! 18 | //! For a VapourSynth plugin example see 19 | //! [sample-plugin](https://github.com/YaLTeR/vapoursynth-rs/blob/master/sample-plugin) which 20 | //! implements some simple filters. 21 | //! 22 | //! ## Short example 23 | //! 24 | //! ```no_run 25 | //! # extern crate vapoursynth; 26 | //! # use anyhow::Error; 27 | //! # #[cfg(all(feature = "vsscript-functions", 28 | //! # feature = "gte-vsscript-api-31", 29 | //! # any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32")))] 30 | //! # fn foo() -> Result<(), Error> { 31 | //! use vapoursynth::prelude::*; 32 | //! 33 | //! let env = Environment::from_file("test.vpy", EvalFlags::SetWorkingDir)?; 34 | //! let node = env.get_output(0)?.0; // Without `.0` for VSScript API 3.0 35 | //! let frame = node.get_frame(0)?; 36 | //! 37 | //! println!("Resolution: {}×{}", frame.width(0), frame.height(0)); 38 | //! # Ok(()) 39 | //! # } 40 | //! # fn main() { 41 | //! # } 42 | //! ``` 43 | //! 44 | //! ## Plugins 45 | //! 46 | //! To make a VapourSynth plugin, start by creating a new Rust library with 47 | //! `crate-type = ["cdylib"]`. Then add filters by implementing the `plugins::Filter` trait. Bind 48 | //! them to functions by implementing `plugins::FilterFunction`, which is much more easily done via 49 | //! the `make_filter_function!` macro. Finally, put `export_vapoursynth_plugin!` at the top level 50 | //! of `src/lib.rs` to export the functionality. 51 | //! 52 | //! **Important note:** due to what seems to be a 53 | //! [bug](https://github.com/rust-lang/rust/issues/50176) in rustc, it's impossible to make plugins 54 | //! on the `i686-pc-windows-gnu` target (all other variations of `x86_64` and `i686` do work). 55 | //! Please use `i686-pc-windows-msvc` for an i686 Windows plugin. 56 | //! 57 | //! ## Short plugin example 58 | //! 59 | //! ```no_run 60 | //! #[macro_use] 61 | //! extern crate vapoursynth; 62 | //! 63 | //! use anyhow::{anyhow, Error}; 64 | //! use vapoursynth::prelude::*; 65 | //! use vapoursynth::core::CoreRef; 66 | //! use vapoursynth::plugins::{Filter, FilterArgument, FrameContext, Metadata}; 67 | //! use vapoursynth::video_info::VideoInfo; 68 | //! 69 | //! // A simple filter that passes the frames through unchanged. 70 | //! struct Passthrough<'core> { 71 | //! source: Node<'core>, 72 | //! } 73 | //! 74 | //! impl<'core> Filter<'core> for Passthrough<'core> { 75 | //! fn video_info(&self, _api: API, _core: CoreRef<'core>) -> Vec> { 76 | //! vec![self.source.info()] 77 | //! } 78 | //! 79 | //! fn get_frame_initial( 80 | //! &self, 81 | //! _api: API, 82 | //! _core: CoreRef<'core>, 83 | //! context: FrameContext, 84 | //! n: usize, 85 | //! ) -> Result>, Error> { 86 | //! self.source.request_frame_filter(context, n); 87 | //! Ok(None) 88 | //! } 89 | //! 90 | //! fn get_frame( 91 | //! &self, 92 | //! _api: API, 93 | //! _core: CoreRef<'core>, 94 | //! context: FrameContext, 95 | //! n: usize, 96 | //! ) -> Result, Error> { 97 | //! self.source 98 | //! .get_frame_filter(context, n) 99 | //! .ok_or(anyhow!("Couldn't get the source frame")) 100 | //! } 101 | //! } 102 | //! 103 | //! make_filter_function! { 104 | //! PassthroughFunction, "Passthrough" 105 | //! 106 | //! fn create_passthrough<'core>( 107 | //! _api: API, 108 | //! _core: CoreRef<'core>, 109 | //! clip: Node<'core>, 110 | //! ) -> Result + 'core>>, Error> { 111 | //! Ok(Some(Box::new(Passthrough { source: clip }))) 112 | //! } 113 | //! } 114 | //! 115 | //! export_vapoursynth_plugin! { 116 | //! Metadata { 117 | //! identifier: "com.example.passthrough", 118 | //! namespace: "passthrough", 119 | //! name: "Example Plugin", 120 | //! read_only: true, 121 | //! }, 122 | //! [PassthroughFunction::new()] 123 | //! } 124 | //! # fn main() { 125 | //! # } 126 | //! ``` 127 | //! 128 | //! Check [sample-plugin](https://github.com/YaLTeR/vapoursynth-rs/blob/master/sample-plugin) for 129 | //! an example plugin which exports some simple filters. 130 | //! 131 | //! ## Supported Versions 132 | //! 133 | //! All VapourSynth and VSScript API versions starting with 3.0 are supported. By default the 134 | //! crates use the 3.0 feature set. To enable higher API version support, enable one of the 135 | //! following Cargo features: 136 | //! 137 | //! * `vapoursynth-api-31` for VapourSynth API 3.1 138 | //! * `vapoursynth-api-32` for VapourSynth API 3.2 139 | //! * `vapoursynth-api-33` for VapourSynth API 3.3 140 | //! * `vapoursynth-api-34` for VapourSynth API 3.4 141 | //! * `vapoursynth-api-35` for VapourSynth API 3.5 142 | //! * `vsscript-api-31` for VSScript API 3.1 143 | //! * `vsscript-api-32` for VSScript API 3.2 144 | //! 145 | //! To enable linking to VapourSynth or VSScript functions, enable the following Cargo features: 146 | //! 147 | //! * `vapoursynth-functions` for VapourSynth functions (`getVapourSynthAPI()`) 148 | //! * `vsscript-functions` for VSScript functions (`vsscript_*()`) 149 | //! 150 | //! ## Building 151 | //! 152 | //! Make sure you have the corresponding libraries available if you enable the linking features. 153 | //! You can use the `VAPOURSYNTH_LIB_DIR` environment variable to specify a custom directory with 154 | //! the library files. 155 | //! 156 | //! On Windows the easiest way is to use the VapourSynth installer (make sure the VapourSynth SDK 157 | //! is checked). The crate should pick up the library directory automatically. If it doesn't or if 158 | //! you're cross-compiling, set `VAPOURSYNTH_LIB_DIR` to 159 | //! `\sdk\lib64` or `<...>\lib32`, depending on the target 160 | //! bitness. 161 | 162 | #![doc(html_root_url = "https://docs.rs/vapoursynth/0.4.0")] 163 | // Preventing all those warnings with #[cfg] directives would be really diffucult. 164 | #![allow(unused, dead_code)] 165 | #![allow(clippy::trivially_copy_pass_by_ref)] 166 | 167 | #[macro_use] 168 | extern crate bitflags; 169 | #[cfg(feature = "f16-pixel-type")] 170 | extern crate half; 171 | #[cfg(any(not(feature = "gte-vsscript-api-32"), test))] 172 | #[macro_use] 173 | extern crate lazy_static; 174 | extern crate vapoursynth_sys; 175 | 176 | #[cfg(feature = "vsscript-functions")] 177 | pub mod vsscript; 178 | 179 | pub mod api; 180 | pub mod component; 181 | pub mod core; 182 | pub mod format; 183 | pub mod frame; 184 | pub mod function; 185 | pub mod map; 186 | pub mod node; 187 | pub mod plugin; 188 | pub mod plugins; 189 | pub mod video_info; 190 | 191 | pub mod prelude { 192 | //! The VapourSynth prelude. 193 | //! 194 | //! Contains the types you most likely want to import anyway. 195 | pub use super::api::{MessageType, API}; 196 | pub use super::component::Component; 197 | pub use super::format::{ColorFamily, PresetFormat, SampleType}; 198 | pub use super::frame::{Frame, FrameRef, FrameRefMut}; 199 | pub use super::map::{Map, OwnedMap, ValueType}; 200 | pub use super::node::{GetFrameError, Node}; 201 | pub use super::plugin::Plugin; 202 | pub use super::video_info::Property; 203 | 204 | #[cfg(feature = "vsscript-functions")] 205 | pub use super::vsscript::{self, Environment, EvalFlags}; 206 | } 207 | 208 | mod tests; 209 | -------------------------------------------------------------------------------- /vapoursynth/src/map/errors.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::NulError; 2 | use std::result; 3 | 4 | use thiserror::Error; 5 | 6 | /// The error type for `Map` operations. 7 | #[derive(Error, Debug, Eq, PartialEq)] 8 | pub enum Error { 9 | #[error("The requested key wasn't found in the map")] 10 | KeyNotFound, 11 | #[error("The requested index was out of bounds")] 12 | IndexOutOfBounds, 13 | #[error("The given/requested value type doesn't match the type of the property")] 14 | WrongValueType, 15 | #[error("The key is invalid")] 16 | InvalidKey(#[from] InvalidKeyError), 17 | #[error("Couldn't convert to a CString")] 18 | CStringConversion(#[from] NulError), 19 | } 20 | 21 | /// A specialized `Result` type for `Map` operations. 22 | pub type Result = result::Result; 23 | 24 | /// An error indicating the map key is invalid. 25 | #[derive(Error, Debug, Eq, PartialEq)] 26 | #[rustfmt::skip] 27 | pub enum InvalidKeyError { 28 | #[error("The key is empty")] 29 | EmptyKey, 30 | #[error("The key contains an invalid character at index {}", _0)] 31 | InvalidCharacter(usize), 32 | } 33 | -------------------------------------------------------------------------------- /vapoursynth/src/map/iterators.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use super::*; 4 | 5 | /// An iterator over the keys of a map. 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct Keys<'map, 'elem: 'map> { 8 | map: &'map Map<'elem>, 9 | count: usize, 10 | index: usize, 11 | } 12 | 13 | impl<'map, 'elem> Keys<'map, 'elem> { 14 | #[inline] 15 | pub(crate) fn new(map: &'map Map<'elem>) -> Self { 16 | Self { 17 | map, 18 | count: map.key_count(), 19 | index: 0, 20 | } 21 | } 22 | } 23 | 24 | impl<'map, 'elem> Iterator for Keys<'map, 'elem> { 25 | type Item = &'map str; 26 | 27 | #[inline] 28 | fn next(&mut self) -> Option { 29 | if self.index == self.count { 30 | return None; 31 | } 32 | 33 | let key = self.map.key(self.index); 34 | self.index += 1; 35 | Some(key) 36 | } 37 | 38 | #[inline] 39 | fn size_hint(&self) -> (usize, Option) { 40 | let len = self.count - self.index; 41 | (len, Some(len)) 42 | } 43 | } 44 | 45 | impl<'map, 'elem> ExactSizeIterator for Keys<'map, 'elem> {} 46 | 47 | /// An iterator over the values associated with a certain key of a map. 48 | #[derive(Debug, Clone)] 49 | pub struct ValueIter<'map, 'elem: 'map, T> { 50 | map: &'map Map<'elem>, 51 | key: CString, 52 | count: i32, 53 | index: i32, 54 | _variance: PhantomData T>, 55 | } 56 | 57 | macro_rules! impl_value_iter { 58 | ($value_type:path, $type:ty, $func:ident) => { 59 | impl<'map, 'elem> ValueIter<'map, 'elem, $type> { 60 | /// Creates a `ValueIter` from the given `map` and `key`. 61 | /// 62 | /// # Safety 63 | /// The caller must ensure `key` is valid. 64 | #[inline] 65 | pub(crate) unsafe fn new(map: &'map Map<'elem>, key: CString) -> Result { 66 | // Check if the value type is correct. 67 | match map.value_type_raw_unchecked(&key)? { 68 | $value_type => {} 69 | _ => return Err(Error::WrongValueType), 70 | }; 71 | 72 | let count = map.value_count_raw_unchecked(&key)? as i32; 73 | Ok(Self { 74 | map, 75 | key, 76 | count, 77 | index: 0, 78 | _variance: PhantomData, 79 | }) 80 | } 81 | } 82 | 83 | impl<'map, 'elem> Iterator for ValueIter<'map, 'elem, $type> { 84 | type Item = $type; 85 | 86 | #[inline] 87 | fn next(&mut self) -> Option { 88 | if self.index == self.count { 89 | return None; 90 | } 91 | 92 | let value = unsafe { self.map.$func(&self.key, self.index).unwrap() }; 93 | self.index += 1; 94 | 95 | Some(value) 96 | } 97 | 98 | #[inline] 99 | fn size_hint(&self) -> (usize, Option) { 100 | let len = (self.count - self.index) as usize; 101 | (len, Some(len)) 102 | } 103 | } 104 | 105 | impl<'map, 'elem> ExactSizeIterator for ValueIter<'map, 'elem, $type> {} 106 | }; 107 | } 108 | 109 | impl_value_iter!(ValueType::Int, i64, get_int_raw_unchecked); 110 | impl_value_iter!(ValueType::Float, f64, get_float_raw_unchecked); 111 | impl_value_iter!(ValueType::Data, &'map [u8], get_data_raw_unchecked); 112 | impl_value_iter!(ValueType::Node, Node<'elem>, get_node_raw_unchecked); 113 | impl_value_iter!(ValueType::Frame, FrameRef<'elem>, get_frame_raw_unchecked); 114 | impl_value_iter!( 115 | ValueType::Function, 116 | Function<'elem>, 117 | get_function_raw_unchecked 118 | ); 119 | -------------------------------------------------------------------------------- /vapoursynth/src/map/value.rs: -------------------------------------------------------------------------------- 1 | use crate::frame::FrameRef; 2 | use crate::function::Function; 3 | use crate::map::{Map, Result, ValueIter}; 4 | use crate::node::Node; 5 | 6 | /// An enumeration of all possible value types. 7 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 8 | pub enum ValueType { 9 | Int, 10 | Float, 11 | Data, 12 | Node, 13 | Frame, 14 | Function, 15 | } 16 | 17 | /// A trait for values which can be stored in a map. 18 | pub trait Value<'map, 'elem: 'map>: Sized { 19 | /// Retrieves the value from the map. 20 | fn get_from_map(map: &'map Map<'elem>, key: &str) -> Result; 21 | 22 | /// Retrieves an iterator over the values from the map. 23 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result>; 24 | 25 | /// Sets the property value in the map. 26 | fn store_in_map(map: &'map mut Map<'elem>, key: &str, x: &Self) -> Result<()>; 27 | 28 | /// Appends the value to the map. 29 | fn append_to_map(map: &'map mut Map<'elem>, key: &str, x: &Self) -> Result<()>; 30 | } 31 | 32 | impl<'map, 'elem: 'map> Value<'map, 'elem> for i64 { 33 | #[inline] 34 | fn get_from_map(map: &Map, key: &str) -> Result { 35 | map.get_int(key) 36 | } 37 | 38 | #[inline] 39 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result> { 40 | map.get_int_iter(key) 41 | } 42 | 43 | #[inline] 44 | fn store_in_map(map: &mut Map, key: &str, x: &Self) -> Result<()> { 45 | map.set_int(key, *x) 46 | } 47 | 48 | #[inline] 49 | fn append_to_map(map: &mut Map, key: &str, x: &Self) -> Result<()> { 50 | map.append_int(key, *x) 51 | } 52 | } 53 | 54 | impl<'map, 'elem: 'map> Value<'map, 'elem> for f64 { 55 | fn get_from_map(map: &Map, key: &str) -> Result { 56 | map.get_float(key) 57 | } 58 | 59 | #[inline] 60 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result> { 61 | map.get_float_iter(key) 62 | } 63 | 64 | #[inline] 65 | fn store_in_map(map: &mut Map, key: &str, x: &Self) -> Result<()> { 66 | map.set_float(key, *x) 67 | } 68 | 69 | #[inline] 70 | fn append_to_map(map: &mut Map, key: &str, x: &Self) -> Result<()> { 71 | map.append_float(key, *x) 72 | } 73 | } 74 | 75 | impl<'map, 'elem: 'map> Value<'map, 'elem> for &'map [u8] { 76 | #[inline] 77 | fn get_from_map(map: &'map Map, key: &str) -> Result { 78 | map.get_data(key) 79 | } 80 | 81 | #[inline] 82 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result> { 83 | map.get_data_iter(key) 84 | } 85 | 86 | #[inline] 87 | fn store_in_map(map: &'map mut Map, key: &str, x: &Self) -> Result<()> { 88 | map.set_data(key, x) 89 | } 90 | 91 | #[inline] 92 | fn append_to_map(map: &'map mut Map, key: &str, x: &Self) -> Result<()> { 93 | map.append_data(key, x) 94 | } 95 | } 96 | 97 | impl<'map, 'elem: 'map> Value<'map, 'elem> for Node<'elem> { 98 | #[inline] 99 | fn get_from_map(map: &Map<'elem>, key: &str) -> Result { 100 | map.get_node(key) 101 | } 102 | 103 | #[inline] 104 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result> { 105 | map.get_node_iter(key) 106 | } 107 | 108 | #[inline] 109 | fn store_in_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> { 110 | map.set_node(key, x) 111 | } 112 | 113 | #[inline] 114 | fn append_to_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> { 115 | map.append_node(key, x) 116 | } 117 | } 118 | 119 | impl<'map, 'elem: 'map> Value<'map, 'elem> for FrameRef<'elem> { 120 | #[inline] 121 | fn get_from_map(map: &Map<'elem>, key: &str) -> Result { 122 | map.get_frame(key) 123 | } 124 | 125 | #[inline] 126 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result> { 127 | map.get_frame_iter(key) 128 | } 129 | 130 | #[inline] 131 | fn store_in_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> { 132 | map.set_frame(key, x) 133 | } 134 | 135 | #[inline] 136 | fn append_to_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> { 137 | map.append_frame(key, x) 138 | } 139 | } 140 | 141 | impl<'map, 'elem: 'map> Value<'map, 'elem> for Function<'elem> { 142 | #[inline] 143 | fn get_from_map(map: &Map<'elem>, key: &str) -> Result { 144 | map.get_function(key) 145 | } 146 | 147 | #[inline] 148 | fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result> { 149 | map.get_function_iter(key) 150 | } 151 | 152 | #[inline] 153 | fn store_in_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> { 154 | map.set_function(key, x) 155 | } 156 | 157 | #[inline] 158 | fn append_to_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> { 159 | map.append_function(key, x) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /vapoursynth/src/node/errors.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::error::Error; 3 | use std::ffi::CStr; 4 | use std::fmt; 5 | 6 | /// A container for a `get_frame` error. 7 | #[derive(Debug)] 8 | pub struct GetFrameError<'a>(Cow<'a, CStr>); 9 | 10 | impl<'a> fmt::Display for GetFrameError<'a> { 11 | #[inline] 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | write!(f, "{}", self.0.to_string_lossy()) 14 | } 15 | } 16 | 17 | impl<'a> Error for GetFrameError<'a> { 18 | #[inline] 19 | fn description(&self) -> &str { 20 | "VapourSynth error" 21 | } 22 | } 23 | 24 | impl<'a> GetFrameError<'a> { 25 | /// Creates a new `GetFrameError` with the given error message. 26 | #[inline] 27 | pub(crate) fn new(message: Cow<'a, CStr>) -> Self { 28 | GetFrameError(message) 29 | } 30 | 31 | /// Consumes this error, returning its underlying error message. 32 | #[inline] 33 | pub fn into_inner(self) -> Cow<'a, CStr> { 34 | self.0 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vapoursynth/src/node/mod.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth nodes. 2 | 3 | use std::borrow::Cow; 4 | use std::ffi::{CStr, CString}; 5 | use std::marker::PhantomData; 6 | use std::os::raw::{c_char, c_void}; 7 | use std::process; 8 | use std::ptr::NonNull; 9 | use std::{mem, panic}; 10 | use vapoursynth_sys as ffi; 11 | 12 | use crate::api::API; 13 | use crate::frame::FrameRef; 14 | use crate::plugins::FrameContext; 15 | use crate::prelude::Property; 16 | use crate::video_info::VideoInfo; 17 | 18 | mod errors; 19 | pub use self::errors::GetFrameError; 20 | 21 | bitflags! { 22 | /// Node flags. 23 | pub struct Flags: i32 { 24 | /// This flag indicates that the frames returned by the filter should not be cached. "Fast" 25 | /// filters should set this to reduce cache bloat. 26 | const NO_CACHE = ffi::VSNodeFlags_nfNoCache.0; 27 | /// This flag must not be used in third-party filters. It is used to mark instances of the 28 | /// built-in Cache filter. Strange things may happen to your filter if you use this flag. 29 | const IS_CACHE = ffi::VSNodeFlags_nfIsCache.0; 30 | 31 | /// This flag should be used by filters which prefer linear access, like source filters, 32 | /// where seeking around can cause significant slowdowns. This flag only has any effect if 33 | /// the filter using it is immediately followed by an instance of the built-in Cache 34 | /// filter. 35 | #[cfg(feature = "gte-vapoursynth-api-33")] 36 | const MAKE_LINEAR = ffi::VSNodeFlags_nfMakeLinear.0; 37 | } 38 | } 39 | 40 | impl From for Flags { 41 | #[inline] 42 | fn from(flags: ffi::VSNodeFlags) -> Self { 43 | Self::from_bits_truncate(flags.0) 44 | } 45 | } 46 | 47 | /// A reference to a node in the constructed filter graph. 48 | #[derive(Debug)] 49 | pub struct Node<'core> { 50 | handle: NonNull, 51 | _owner: PhantomData<&'core ()>, 52 | } 53 | 54 | unsafe impl<'core> Send for Node<'core> {} 55 | unsafe impl<'core> Sync for Node<'core> {} 56 | 57 | impl<'core> Drop for Node<'core> { 58 | #[inline] 59 | fn drop(&mut self) { 60 | unsafe { 61 | API::get_cached().free_node(self.handle.as_ptr()); 62 | } 63 | } 64 | } 65 | 66 | impl<'core> Clone for Node<'core> { 67 | #[inline] 68 | fn clone(&self) -> Self { 69 | let handle = unsafe { API::get_cached().clone_node(self.handle.as_ptr()) }; 70 | Self { 71 | handle: unsafe { NonNull::new_unchecked(handle) }, 72 | _owner: PhantomData, 73 | } 74 | } 75 | } 76 | 77 | impl<'core> Node<'core> { 78 | /// Wraps `handle` in a `Node`. 79 | /// 80 | /// # Safety 81 | /// The caller must ensure `handle` and the lifetime is valid and API is cached. 82 | #[inline] 83 | pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSNodeRef) -> Self { 84 | Self { 85 | handle: NonNull::new_unchecked(handle), 86 | _owner: PhantomData, 87 | } 88 | } 89 | 90 | /// Returns the underlying pointer. 91 | #[inline] 92 | pub(crate) fn ptr(&self) -> *mut ffi::VSNodeRef { 93 | self.handle.as_ptr() 94 | } 95 | 96 | /// Returns the video info associated with this `Node`. 97 | // Since we don't store the pointer to the actual `ffi::VSVideoInfo` and the lifetime is that 98 | // of the `ffi::VSFormat`, this returns `VideoInfo<'core>` rather than `VideoInfo<'a>`. 99 | #[inline] 100 | pub fn info(&self) -> VideoInfo<'core> { 101 | unsafe { 102 | let ptr = API::get_cached().get_video_info(self.handle.as_ptr()); 103 | VideoInfo::from_ptr(ptr) 104 | } 105 | } 106 | 107 | /// Generates a frame directly. 108 | /// 109 | /// The `'error` lifetime is unbounded because this function always returns owned data. 110 | /// 111 | /// # Panics 112 | /// Panics is `n` is greater than `i32::MAX`. 113 | pub fn get_frame<'error>(&self, n: usize) -> Result, GetFrameError<'error>> { 114 | assert!(n <= i32::MAX as usize); 115 | 116 | let vi = &self.info(); 117 | 118 | #[cfg(not(feature = "gte-vapoursynth-api-32"))] 119 | if let Property::Constant(total) = vi.num_frames { 120 | if n >= total { 121 | let err_cstring = 122 | CString::new("Requested frame number beyond the last one").unwrap(); 123 | return Err(GetFrameError::new(Cow::Owned(err_cstring))); 124 | } 125 | } 126 | 127 | #[cfg(feature = "gte-vapoursynth-api-32")] 128 | if n >= vi.num_frames { 129 | let err_cstring = CString::new("Requested frame number beyond the last one").unwrap(); 130 | return Err(GetFrameError::new(Cow::Owned(err_cstring))); 131 | } 132 | 133 | // Kinda arbitrary. Same value as used in vsvfw. 134 | const ERROR_BUF_CAPACITY: usize = 32 * 1024; 135 | 136 | let mut err_buf = vec![0; ERROR_BUF_CAPACITY]; 137 | let mut err_buf = err_buf.into_boxed_slice(); 138 | 139 | let handle = 140 | unsafe { API::get_cached().get_frame(n as i32, self.handle.as_ptr(), &mut err_buf) }; 141 | 142 | if handle.is_null() { 143 | // TODO: remove this extra allocation by reusing `Box<[c_char]>`. 144 | let error = unsafe { CStr::from_ptr(err_buf.as_ptr()) }.to_owned(); 145 | Err(GetFrameError::new(Cow::Owned(error))) 146 | } else { 147 | Ok(unsafe { FrameRef::from_ptr(handle) }) 148 | } 149 | } 150 | 151 | /// Requests the generation of a frame. When the frame is ready, a user-provided function is 152 | /// called. 153 | /// 154 | /// If multiple frames were requested, they can be returned in any order. 155 | /// 156 | /// The callback arguments are: 157 | /// 158 | /// - the generated frame or an error message if the generation failed, 159 | /// - the frame number (equal to `n`), 160 | /// - the node that generated the frame (the same as `self`). 161 | /// 162 | /// If the callback panics, the process is aborted. 163 | /// 164 | /// # Panics 165 | /// Panics is `n` is greater than `i32::MAX`. 166 | pub fn get_frame_async(&self, n: usize, callback: F) 167 | where 168 | F: FnOnce(Result, GetFrameError>, usize, Node<'core>) + Send + 'core, 169 | { 170 | struct CallbackData<'core> { 171 | callback: Box + 'core>, 172 | } 173 | 174 | // A little bit of magic for Box. 175 | trait CallbackFn<'core> { 176 | fn call( 177 | self: Box, 178 | frame: Result, GetFrameError>, 179 | n: usize, 180 | node: Node<'core>, 181 | ); 182 | } 183 | 184 | impl<'core, F> CallbackFn<'core> for F 185 | where 186 | F: FnOnce(Result, GetFrameError>, usize, Node<'core>), 187 | { 188 | #[allow(clippy::boxed_local)] 189 | fn call( 190 | self: Box, 191 | frame: Result, GetFrameError>, 192 | n: usize, 193 | node: Node<'core>, 194 | ) { 195 | (self)(frame, n, node) 196 | } 197 | } 198 | 199 | unsafe extern "system" fn c_callback( 200 | user_data: *mut c_void, 201 | frame: *const ffi::VSFrameRef, 202 | n: i32, 203 | node: *mut ffi::VSNodeRef, 204 | error_msg: *const c_char, 205 | ) { 206 | // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of 207 | // retrieving it. 208 | let user_data = Box::from_raw(user_data as *mut CallbackData<'static>); 209 | 210 | let closure = panic::AssertUnwindSafe(move || { 211 | let frame = if frame.is_null() { 212 | debug_assert!(!error_msg.is_null()); 213 | let error_msg = Cow::Borrowed(CStr::from_ptr(error_msg)); 214 | Err(GetFrameError::new(error_msg)) 215 | } else { 216 | debug_assert!(error_msg.is_null()); 217 | Ok(FrameRef::from_ptr(frame)) 218 | }; 219 | 220 | let node = Node::from_ptr(node); 221 | 222 | debug_assert!(n >= 0); 223 | let n = n as usize; 224 | 225 | user_data.callback.call(frame, n, node); 226 | }); 227 | 228 | if panic::catch_unwind(closure).is_err() { 229 | process::abort(); 230 | } 231 | } 232 | 233 | assert!(n <= i32::MAX as usize); 234 | let n = n as i32; 235 | 236 | let user_data = Box::new(CallbackData { 237 | callback: Box::new(callback), 238 | }); 239 | 240 | let new_node = self.clone(); 241 | 242 | unsafe { 243 | API::get_cached().get_frame_async( 244 | n, 245 | new_node.handle.as_ptr(), 246 | Some(c_callback), 247 | Box::into_raw(user_data) as *mut c_void, 248 | ); 249 | } 250 | 251 | // It'll be dropped by the callback. 252 | mem::forget(new_node); 253 | } 254 | 255 | /// Requests a frame from a node and returns immediately. 256 | /// 257 | /// This is only used in filters' "get frame" functions. 258 | /// 259 | /// A filter usually calls this function from `get_frame_initial()`. The requested frame can 260 | /// then be retrieved using `get_frame_filter()` from within filter's `get_frame()` function. 261 | /// 262 | /// It is safe to request a frame more than once. An unimportant consequence of requesting a 263 | /// frame more than once is that the filter's `get_frame()` function may be called more than 264 | /// once for the same frame. 265 | /// 266 | /// It is best to request frames in ascending order, i.e. `n`, `n+1`, `n+2`, etc. 267 | /// 268 | /// # Panics 269 | /// Panics is `n` is greater than `i32::MAX`. 270 | pub fn request_frame_filter(&self, context: FrameContext, n: usize) { 271 | assert!(n <= i32::MAX as usize); 272 | let n = n as i32; 273 | 274 | unsafe { 275 | API::get_cached().request_frame_filter(n, self.ptr(), context.ptr()); 276 | } 277 | } 278 | 279 | /// Retrieves a frame that was previously requested with `request_frame_filter()`. 280 | /// 281 | /// A filter usually calls this function from `get_frame()`. It is safe to retrieve a frame 282 | /// more than once. 283 | /// 284 | /// # Panics 285 | /// Panics is `n` is greater than `i32::MAX`. 286 | pub fn get_frame_filter(&self, context: FrameContext, n: usize) -> Option> { 287 | assert!(n <= i32::MAX as usize); 288 | let n = n as i32; 289 | 290 | let ptr = unsafe { API::get_cached().get_frame_filter(n, self.ptr(), context.ptr()) }; 291 | if ptr.is_null() { 292 | None 293 | } else { 294 | Some(unsafe { FrameRef::from_ptr(ptr) }) 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /vapoursynth/src/plugin.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth plugins. 2 | 3 | use std::ffi::{CStr, CString, NulError}; 4 | use std::marker::PhantomData; 5 | use std::ops::Deref; 6 | use std::ptr::NonNull; 7 | use vapoursynth_sys as ffi; 8 | 9 | use crate::api::API; 10 | use crate::map::{Map, OwnedMap}; 11 | use crate::plugins::{self, FilterFunction}; 12 | 13 | /// A VapourSynth plugin. 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct Plugin<'core> { 16 | handle: NonNull, 17 | _owner: PhantomData<&'core ()>, 18 | } 19 | 20 | unsafe impl<'core> Send for Plugin<'core> {} 21 | unsafe impl<'core> Sync for Plugin<'core> {} 22 | 23 | impl<'core> Plugin<'core> { 24 | /// Wraps `handle` in a `Plugin`. 25 | /// 26 | /// # Safety 27 | /// The caller must ensure `handle` is valid and API is cached. 28 | #[inline] 29 | pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSPlugin) -> Self { 30 | Self { 31 | handle: NonNull::new_unchecked(handle), 32 | _owner: PhantomData, 33 | } 34 | } 35 | 36 | /// Returns a map containing a list of the filters exported by a plugin. 37 | /// 38 | /// Keys: the filter names; 39 | /// 40 | /// Values: the filter name followed by its argument string, separated by a semicolon. 41 | // TODO: parse the values on the crate side and return a nice struct. 42 | #[inline] 43 | pub fn functions(&self) -> OwnedMap<'core> { 44 | unsafe { OwnedMap::from_ptr(API::get_cached().get_functions(self.handle.as_ptr())) } 45 | } 46 | 47 | /// Returns the absolute path to the plugin, including the plugin's file name. This is the real 48 | /// location of the plugin, i.e. there are no symbolic links in the path. 49 | /// 50 | /// Path elements are always delimited with forward slashes. 51 | #[cfg(feature = "gte-vapoursynth-api-31")] 52 | #[inline] 53 | pub fn path(&self) -> Option<&'core CStr> { 54 | let ptr = unsafe { API::get_cached().get_plugin_path(self.handle.as_ptr()) }; 55 | if ptr.is_null() { 56 | None 57 | } else { 58 | Some(unsafe { CStr::from_ptr(ptr) }) 59 | } 60 | } 61 | 62 | /// Invokes a filter. 63 | /// 64 | /// `invoke()` makes sure the filter has no compat input nodes, checks that the args passed to 65 | /// the filter are consistent with the argument list registered by the plugin that contains the 66 | /// filter, creates the filter, and checks that the filter doesn't return any compat nodes. If 67 | /// everything goes smoothly, the filter will be ready to generate frames after `invoke()` 68 | /// returns. 69 | /// 70 | /// Returns a map containing the filter's return value(s). Use `Map::error()` to check if the 71 | /// filter was invoked successfully. 72 | /// 73 | /// Most filters will either add an error to the map, or one or more clips with the key `clip`. 74 | /// The exception to this are functions, for example `LoadPlugin`, which doesn't return any 75 | /// clips for obvious reasons. 76 | #[inline] 77 | pub fn invoke(&self, name: &str, args: &Map<'core>) -> Result, NulError> { 78 | let name = CString::new(name)?; 79 | Ok(unsafe { 80 | OwnedMap::from_ptr(API::get_cached().invoke( 81 | self.handle.as_ptr(), 82 | name.as_ptr(), 83 | args.deref(), 84 | )) 85 | }) 86 | } 87 | 88 | /// Registers a filter function to be exported by a non-readonly plugin. 89 | #[inline] 90 | pub fn register_function(&self, filter_function: F) -> Result<(), NulError> { 91 | // TODO: this is almost the same code as plugins::ffi::call_register_function(). 92 | let name_cstring = CString::new(filter_function.name())?; 93 | let args_cstring = CString::new(filter_function.args())?; 94 | 95 | let data = Box::new(plugins::ffi::FilterFunctionData:: { 96 | filter_function, 97 | name: name_cstring, 98 | }); 99 | 100 | unsafe { 101 | API::get_cached().register_function( 102 | data.name.as_ptr(), 103 | args_cstring.as_ptr(), 104 | plugins::ffi::create::, 105 | Box::into_raw(data) as _, 106 | self.handle.as_ptr(), 107 | ); 108 | } 109 | 110 | Ok(()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /vapoursynth/src/plugins/ffi.rs: -------------------------------------------------------------------------------- 1 | //! Internal stuff for plugin FFI handling. 2 | use std::ffi::CString; 3 | use std::fmt::Write; 4 | use std::ops::{Deref, DerefMut}; 5 | use std::os::raw::c_void; 6 | use std::ptr::{self, NonNull}; 7 | use std::{mem, panic, process}; 8 | 9 | use vapoursynth_sys as ffi; 10 | 11 | use thiserror::Error; 12 | 13 | use crate::api::API; 14 | use crate::core::CoreRef; 15 | use crate::frame::FrameRef; 16 | use crate::map::{Map, MapRef, MapRefMut}; 17 | use crate::plugins::{Filter, FilterFunction, FrameContext, Metadata}; 18 | use crate::video_info::VideoInfo; 19 | 20 | /// Container for the internal filter function data. 21 | pub(crate) struct FilterFunctionData { 22 | pub filter_function: F, 23 | // Store the name since it's supposed to be the same between two invocations (register and 24 | // create_filter). 25 | pub name: CString, 26 | } 27 | 28 | /// Sets the video info of the output node of this filter. 29 | unsafe extern "system" fn init( 30 | _in_: *mut ffi::VSMap, 31 | out: *mut ffi::VSMap, 32 | instance_data: *mut *mut c_void, 33 | node: *mut ffi::VSNode, 34 | core: *mut ffi::VSCore, 35 | _vsapi: *const ffi::VSAPI, 36 | ) { 37 | let closure = move || { 38 | let core = CoreRef::from_ptr(core); 39 | // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of 40 | // retrieving it. 41 | let filter = 42 | Box::from_raw(*(instance_data as *mut *mut Box + 'static>)); 43 | 44 | let vi = filter 45 | .video_info(API::get_cached(), core) 46 | .into_iter() 47 | .map(VideoInfo::ffi_type) 48 | .collect::>(); 49 | API::get_cached().set_video_info(&vi, node); 50 | 51 | mem::forget(filter); 52 | }; 53 | 54 | if panic::catch_unwind(closure).is_err() { 55 | let closure = move || { 56 | // We have to leak filter here because we can't guarantee that it's in a consistent 57 | // state after a panic. 58 | // 59 | // Just set the error message. 60 | let mut out = MapRefMut::from_ptr(out); 61 | out.set_error("Panic during Filter::video_info()"); 62 | }; 63 | 64 | if panic::catch_unwind(closure).is_err() { 65 | process::abort(); 66 | } 67 | } 68 | } 69 | 70 | /// Drops the filter. 71 | unsafe extern "system" fn free( 72 | instance_data: *mut c_void, 73 | core: *mut ffi::VSCore, 74 | _vsapi: *const ffi::VSAPI, 75 | ) { 76 | let closure = move || { 77 | // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of 78 | // retrieving it. 79 | let filter = Box::from_raw(instance_data as *mut Box + 'static>); 80 | drop(filter); 81 | }; 82 | 83 | if panic::catch_unwind(closure).is_err() { 84 | process::abort(); 85 | } 86 | } 87 | 88 | /// Calls `Filter::get_frame_initial()` and `Filter::get_frame()`. 89 | unsafe extern "system" fn get_frame( 90 | n: i32, 91 | activation_reason: i32, 92 | instance_data: *mut *mut c_void, 93 | _frame_data: *mut *mut c_void, 94 | frame_ctx: *mut ffi::VSFrameContext, 95 | core: *mut ffi::VSCore, 96 | _vsapi: *const ffi::VSAPI, 97 | ) -> *const ffi::VSFrameRef { 98 | let closure = move || { 99 | let api = API::get_cached(); 100 | let core = CoreRef::from_ptr(core); 101 | let context = FrameContext::from_ptr(frame_ctx); 102 | 103 | // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of 104 | // retrieving it. 105 | let filter = 106 | Box::from_raw(*(instance_data as *mut *mut Box + 'static>)); 107 | 108 | debug_assert!(n >= 0); 109 | let n = n as usize; 110 | 111 | let rv = match activation_reason { 112 | x if x == ffi::VSActivationReason::arInitial as _ => { 113 | match filter.get_frame_initial(api, core, context, n) { 114 | Ok(Some(frame)) => { 115 | let ptr = frame.deref().deref() as *const _; 116 | // The ownership is transferred to the caller. 117 | mem::forget(frame); 118 | ptr 119 | } 120 | Ok(None) => ptr::null(), 121 | Err(err) => { 122 | let mut buf = String::with_capacity(64); 123 | 124 | write!(buf, "Error in Filter::get_frame_initial(): {}", err); 125 | 126 | write!(buf, "{}", err); 127 | 128 | let buf = CString::new(buf.replace('\0', "\\0")).unwrap(); 129 | api.set_filter_error(buf.as_ptr(), frame_ctx); 130 | 131 | ptr::null() 132 | } 133 | } 134 | } 135 | x if x == ffi::VSActivationReason::arAllFramesReady as _ => { 136 | match filter.get_frame(api, core, context, n) { 137 | Ok(frame) => { 138 | let ptr = frame.deref().deref() as *const _; 139 | // The ownership is transferred to the caller. 140 | mem::forget(frame); 141 | ptr 142 | } 143 | Err(err) => { 144 | let buf = format!("{}", err); 145 | let buf = CString::new(buf.replace('\0', "\\0")).unwrap(); 146 | api.set_filter_error(buf.as_ptr(), frame_ctx); 147 | 148 | ptr::null() 149 | } 150 | } 151 | } 152 | _ => ptr::null(), 153 | }; 154 | 155 | mem::forget(filter); 156 | 157 | rv 158 | }; 159 | 160 | match panic::catch_unwind(closure) { 161 | Ok(frame) => frame, 162 | Err(_) => process::abort(), 163 | } 164 | } 165 | 166 | /// Creates a new instance of the filter. 167 | pub(crate) unsafe extern "system" fn create( 168 | in_: *const ffi::VSMap, 169 | out: *mut ffi::VSMap, 170 | user_data: *mut c_void, 171 | core: *mut ffi::VSCore, 172 | api: *const ffi::VSAPI, 173 | ) { 174 | let closure = move || { 175 | API::set(api); 176 | 177 | let args = MapRef::from_ptr(in_); 178 | let mut out = MapRefMut::from_ptr(out); 179 | let core = CoreRef::from_ptr(core); 180 | let data = Box::from_raw(user_data as *mut FilterFunctionData); 181 | 182 | let filter = match data.filter_function.create(API::get_cached(), core, &args) { 183 | Ok(Some(filter)) => Some(Box::new(filter)), 184 | Ok(None) => None, 185 | Err(err) => { 186 | let mut buf = String::with_capacity(64); 187 | 188 | write!( 189 | buf, 190 | "Error in Filter::create() of {}: {}", 191 | data.name.to_str().unwrap(), 192 | err 193 | ); 194 | 195 | write!(buf, "{}", err); 196 | 197 | out.set_error(&buf.replace('\0', "\\0")).unwrap(); 198 | None 199 | } 200 | }; 201 | 202 | if let Some(filter) = filter { 203 | API::get_cached().create_filter( 204 | in_, 205 | out.deref_mut().deref_mut(), 206 | data.name.as_ptr(), 207 | init, 208 | get_frame, 209 | Some(free), 210 | ffi::VSFilterMode::fmParallel, 211 | ffi::VSNodeFlags(0), 212 | Box::into_raw(filter) as *mut _, 213 | core.ptr(), 214 | ); 215 | } 216 | 217 | mem::forget(data); 218 | }; 219 | 220 | if panic::catch_unwind(closure).is_err() { 221 | // The `FilterFunction` might have been left in an inconsistent state, so we have to abort. 222 | process::abort(); 223 | } 224 | } 225 | 226 | /// Registers the plugin. 227 | /// 228 | /// This function is for internal use only. 229 | /// 230 | /// # Safety 231 | /// The caller must ensure the pointers are valid. 232 | #[inline] 233 | pub unsafe fn call_config_func( 234 | config_func: *const c_void, 235 | plugin: *mut c_void, 236 | metadata: Metadata, 237 | ) { 238 | let config_func = *(&config_func as *const _ as *const ffi::VSConfigPlugin); 239 | 240 | let identifier_cstring = CString::new(metadata.identifier) 241 | .expect("Couldn't convert the plugin identifier to a CString"); 242 | let namespace_cstring = CString::new(metadata.namespace) 243 | .expect("Couldn't convert the plugin namespace to a CString"); 244 | let name_cstring = 245 | CString::new(metadata.name).expect("Couldn't convert the plugin name to a CString"); 246 | 247 | config_func( 248 | identifier_cstring.as_ptr(), 249 | namespace_cstring.as_ptr(), 250 | name_cstring.as_ptr(), 251 | ffi::VAPOURSYNTH_API_VERSION, 252 | if metadata.read_only { 1 } else { 0 }, 253 | plugin as *mut ffi::VSPlugin, 254 | ); 255 | } 256 | 257 | /// Registers the filter `F`. 258 | /// 259 | /// This function is for internal use only. 260 | /// 261 | /// # Safety 262 | /// The caller must ensure the pointers are valid. 263 | #[inline] 264 | pub unsafe fn call_register_func( 265 | register_func: *const c_void, 266 | plugin: *mut c_void, 267 | filter_function: F, 268 | ) { 269 | let register_func = *(®ister_func as *const _ as *const ffi::VSRegisterFunction); 270 | 271 | let name_cstring = CString::new(filter_function.name()) 272 | .expect("Couldn't convert the filter name to a CString"); 273 | let args_cstring = CString::new(filter_function.args()) 274 | .expect("Couldn't convert the filter args to a CString"); 275 | 276 | let data = Box::new(FilterFunctionData { 277 | filter_function, 278 | name: name_cstring, 279 | }); 280 | 281 | register_func( 282 | data.name.as_ptr(), 283 | args_cstring.as_ptr(), 284 | create::, 285 | Box::into_raw(data) as _, 286 | plugin as *mut ffi::VSPlugin, 287 | ); 288 | } 289 | 290 | /// Exports a VapourSynth plugin from this library. 291 | /// 292 | /// This macro should be used only once at the top level of the library. The library should have a 293 | /// `cdylib` crate type. 294 | /// 295 | /// The first parameter is a `Metadata` expression containing your plugin's metadata. 296 | /// 297 | /// Following it is a list of values implementing `FilterFunction`, those are the filter functions 298 | /// the plugin will export. 299 | /// 300 | /// # Example 301 | /// ```ignore 302 | /// export_vapoursynth_plugin! { 303 | /// Metadata { 304 | /// identifier: "com.example.invert", 305 | /// namespace: "invert", 306 | /// name: "Invert Example Plugin", 307 | /// read_only: true, 308 | /// }, 309 | /// [SampleFilterFunction::new(), OtherFunction::new()] 310 | /// } 311 | /// ``` 312 | #[macro_export] 313 | macro_rules! export_vapoursynth_plugin { 314 | ($metadata:expr, [$($filter:expr),*$(,)*]) => ( 315 | use ::std::os::raw::c_void; 316 | 317 | #[allow(non_snake_case)] 318 | #[no_mangle] 319 | pub unsafe extern "system" fn VapourSynthPluginInit( 320 | config_func: *const c_void, 321 | register_func: *const c_void, 322 | plugin: *mut c_void, 323 | ) { 324 | use ::std::{panic, process}; 325 | use $crate::plugins::ffi::{call_config_func, call_register_func}; 326 | 327 | let closure = move || { 328 | call_config_func(config_func, plugin, $metadata); 329 | 330 | $(call_register_func(register_func, plugin, $filter);)* 331 | }; 332 | 333 | if panic::catch_unwind(closure).is_err() { 334 | process::abort(); 335 | } 336 | } 337 | ) 338 | } 339 | -------------------------------------------------------------------------------- /vapoursynth/src/plugins/frame_context.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::ptr::NonNull; 3 | use vapoursynth_sys as ffi; 4 | 5 | use crate::api::API; 6 | 7 | /// A frame context used in filters. 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct FrameContext<'a> { 10 | handle: NonNull, 11 | _owner: PhantomData<&'a ()>, 12 | } 13 | 14 | impl<'a> FrameContext<'a> { 15 | /// Wraps `handle` in a `FrameContext`. 16 | /// 17 | /// # Safety 18 | /// The caller must ensure `handle` is valid and API is cached. 19 | #[inline] 20 | pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSFrameContext) -> Self { 21 | Self { 22 | handle: NonNull::new_unchecked(handle), 23 | _owner: PhantomData, 24 | } 25 | } 26 | 27 | /// Returns the underlying pointer. 28 | #[inline] 29 | pub(crate) fn ptr(self) -> *mut ffi::VSFrameContext { 30 | self.handle.as_ptr() 31 | } 32 | 33 | /// Returns the index of the node from which the frame is being requested. 34 | #[inline] 35 | pub fn output_index(self) -> usize { 36 | let index = unsafe { API::get_cached().get_output_index(self.handle.as_ptr()) }; 37 | debug_assert!(index >= 0); 38 | index as _ 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vapoursynth/src/plugins/mod.rs: -------------------------------------------------------------------------------- 1 | //! Things related to making VapourSynth plugins. 2 | 3 | use anyhow::Error; 4 | 5 | use crate::api::API; 6 | use crate::core::CoreRef; 7 | use crate::frame::FrameRef; 8 | use crate::function::Function; 9 | use crate::map::{self, Map, Value, ValueIter}; 10 | use crate::node::Node; 11 | use crate::video_info::VideoInfo; 12 | 13 | mod frame_context; 14 | pub use self::frame_context::FrameContext; 15 | 16 | pub mod ffi; 17 | 18 | /// Plugin metadata. 19 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 20 | pub struct Metadata { 21 | /// A "reverse" URL, unique among all plugins. 22 | /// 23 | /// For example, `com.example.invert`. 24 | pub identifier: &'static str, 25 | 26 | /// Namespace where the plugin's filters will go, unique among all plugins. 27 | /// 28 | /// Only lowercase letters and the underscore should be used, and it shouldn't be too long. 29 | /// Additionally, words that are special to Python, e.g. `del`, should be avoided. 30 | /// 31 | /// For example, `invert`. 32 | pub namespace: &'static str, 33 | 34 | /// Plugin name in readable form. 35 | /// 36 | /// For example, `Invert Example Plugin`. 37 | pub name: &'static str, 38 | 39 | /// Whether new filters can be registered at runtime. 40 | /// 41 | /// This should generally be set to `false`. It's used for the built-in AviSynth compat plugin. 42 | pub read_only: bool, 43 | } 44 | 45 | /// A filter function interface. 46 | /// 47 | /// See the `make_filter_function!` macro that generates types implementing this automatically. 48 | pub trait FilterFunction: Send + Sync { 49 | /// Returns the name of the function. 50 | /// 51 | /// The characters allowed are letters, numbers, and the underscore. The first character must 52 | /// be a letter. In other words: `^[a-zA-Z][a-zA-Z0-9_]*$`. 53 | /// 54 | /// For example, `Invert`. 55 | fn name(&self) -> &str; 56 | 57 | /// Returns the argument string. 58 | /// 59 | /// Arguments are separated by a semicolon. Each argument is made of several fields separated 60 | /// by a colon. Don’t insert additional whitespace characters, or VapourSynth will die. 61 | /// 62 | /// Fields: 63 | /// - The argument name. The same characters are allowed as for the filter's name. Argument 64 | /// names should be all lowercase and use only letters and the underscore. 65 | /// 66 | /// - The type. One of `int`, `float`, `data`, `clip`, `frame`, `func`. They correspond to the 67 | /// `Map::get_*()` functions (`clip` is `get_node()`). It's possible to declare an array by 68 | /// appending `[]` to the type. 69 | /// 70 | /// - `opt` if the parameter is optional. 71 | /// 72 | /// - `empty` if the array is allowed to be empty. 73 | /// 74 | /// The following example declares the arguments "blah", "moo", and "asdf": 75 | /// `blah:clip;moo:int[]:opt;asdf:float:opt;` 76 | fn args(&self) -> &str; 77 | 78 | /// The callback for this filter function. 79 | /// 80 | /// In most cases this is where you should create a new instance of the filter and return it. 81 | /// However, a filter function like AviSynth compat's `LoadPlugin()` which isn't actually a 82 | /// filter, can return `None`. 83 | /// 84 | /// `args` contains the filter arguments, as specified by the argument string from 85 | /// `FilterFunction::args()`. Their presence and types are validated by VapourSynth so it's 86 | /// safe to `unwrap()`. 87 | /// 88 | /// In this function you should take all input nodes for your filter and store them somewhere 89 | /// so that you can request their frames in `get_frame_initial()`. 90 | // TODO: with generic associated types it'll be possible to make Filter<'core> an associated 91 | // type of this trait and get rid of this Box. 92 | fn create<'core>( 93 | &self, 94 | api: API, 95 | core: CoreRef<'core>, 96 | args: &Map<'core>, 97 | ) -> Result + 'core>>, Error>; 98 | } 99 | 100 | /// A filter interface. 101 | // TODO: perhaps it's possible to figure something out about Send + Sync with specialization? Since 102 | // there are Node flags which say that the filter will be called strictly by one thread, in which 103 | // case Sync shouldn't be required. 104 | pub trait Filter<'core>: Send + Sync { 105 | /// Returns the parameters of this filter's output node. 106 | /// 107 | /// The returned vector should contain one entry for each node output index. 108 | fn video_info(&self, api: API, core: CoreRef<'core>) -> Vec>; 109 | 110 | /// Requests the necessary frames from downstream nodes. 111 | /// 112 | /// This is always the first function to get called for a given frame `n`. 113 | /// 114 | /// In this function you should call `request_frame_filter()` on any input nodes that you need 115 | /// and return `None`. If you do not need any input frames, you should generate the output 116 | /// frame and return it here. 117 | /// 118 | /// Do not call `Node::get_frame()` from within this function. 119 | fn get_frame_initial( 120 | &self, 121 | api: API, 122 | core: CoreRef<'core>, 123 | context: FrameContext, 124 | n: usize, 125 | ) -> Result>, Error>; 126 | 127 | /// Returns the requested frame. 128 | /// 129 | /// This is always the second function to get called for a given frame `n`. If the frame was 130 | /// retrned from `get_frame_initial()`, this function is not called. 131 | /// 132 | /// In this function you should call `get_frame_filter()` on the input nodes to retrieve the 133 | /// frames you requested in `get_frame_initial()`. 134 | /// 135 | /// Do not call `Node::get_frame()` from within this function. 136 | fn get_frame( 137 | &self, 138 | api: API, 139 | core: CoreRef<'core>, 140 | context: FrameContext, 141 | n: usize, 142 | ) -> Result, Error>; 143 | } 144 | 145 | /// An internal trait representing a filter argument type. 146 | pub trait FilterArgument<'map, 'elem: 'map>: Value<'map, 'elem> + private::Sealed { 147 | /// Returns the VapourSynth type name for this argument type. 148 | fn type_name() -> &'static str; 149 | } 150 | 151 | /// An internal trait representing a filter parameter type (argument type + whether it's an array 152 | /// or optional). 153 | pub trait FilterParameter<'map, 'elem: 'map>: private::Sealed { 154 | /// The underlying argument type for this parameter type. 155 | type Argument: FilterArgument<'map, 'elem>; 156 | 157 | /// Returns whether this parameter is an array. 158 | fn is_array() -> bool; 159 | 160 | /// Returns whether this parameter is optional. 161 | fn is_optional() -> bool; 162 | 163 | /// Retrieves this parameter from the given map. 164 | fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self; 165 | } 166 | 167 | impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for i64 { 168 | #[inline] 169 | fn type_name() -> &'static str { 170 | "int" 171 | } 172 | } 173 | 174 | impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for f64 { 175 | #[inline] 176 | fn type_name() -> &'static str { 177 | "float" 178 | } 179 | } 180 | 181 | impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for &'map [u8] { 182 | #[inline] 183 | fn type_name() -> &'static str { 184 | "data" 185 | } 186 | } 187 | 188 | impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for Node<'elem> { 189 | #[inline] 190 | fn type_name() -> &'static str { 191 | "clip" 192 | } 193 | } 194 | 195 | impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for FrameRef<'elem> { 196 | #[inline] 197 | fn type_name() -> &'static str { 198 | "frame" 199 | } 200 | } 201 | 202 | impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for Function<'elem> { 203 | #[inline] 204 | fn type_name() -> &'static str { 205 | "func" 206 | } 207 | } 208 | 209 | impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for T 210 | where 211 | T: FilterArgument<'map, 'elem>, 212 | { 213 | type Argument = Self; 214 | 215 | #[inline] 216 | fn is_array() -> bool { 217 | false 218 | } 219 | 220 | #[inline] 221 | fn is_optional() -> bool { 222 | false 223 | } 224 | 225 | #[inline] 226 | fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self { 227 | Self::get_from_map(map, key).unwrap() 228 | } 229 | } 230 | 231 | impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for Option 232 | where 233 | T: FilterArgument<'map, 'elem>, 234 | { 235 | type Argument = T; 236 | 237 | #[inline] 238 | fn is_array() -> bool { 239 | false 240 | } 241 | 242 | #[inline] 243 | fn is_optional() -> bool { 244 | true 245 | } 246 | 247 | #[inline] 248 | fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self { 249 | match ::get_from_map(map, key) { 250 | Ok(x) => Some(x), 251 | Err(map::Error::KeyNotFound) => None, 252 | _ => unreachable!(), 253 | } 254 | } 255 | } 256 | 257 | impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for ValueIter<'map, 'elem, T> 258 | where 259 | T: FilterArgument<'map, 'elem>, 260 | { 261 | type Argument = T; 262 | 263 | #[inline] 264 | fn is_array() -> bool { 265 | true 266 | } 267 | 268 | #[inline] 269 | fn is_optional() -> bool { 270 | false 271 | } 272 | 273 | #[inline] 274 | fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self { 275 | ::get_iter_from_map(map, key).unwrap() 276 | } 277 | } 278 | 279 | impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for Option> 280 | where 281 | T: FilterArgument<'map, 'elem>, 282 | { 283 | type Argument = T; 284 | 285 | #[inline] 286 | fn is_array() -> bool { 287 | true 288 | } 289 | 290 | #[inline] 291 | fn is_optional() -> bool { 292 | true 293 | } 294 | 295 | #[inline] 296 | fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self { 297 | match ::get_iter_from_map(map, key) { 298 | Ok(x) => Some(x), 299 | Err(map::Error::KeyNotFound) => None, 300 | _ => unreachable!(), 301 | } 302 | } 303 | } 304 | 305 | mod private { 306 | use super::{FilterArgument, FrameRef, Function, Node, ValueIter}; 307 | 308 | pub trait Sealed {} 309 | 310 | impl Sealed for i64 {} 311 | impl Sealed for f64 {} 312 | impl<'map> Sealed for &'map [u8] {} 313 | impl<'elem> Sealed for Node<'elem> {} 314 | impl<'elem> Sealed for FrameRef<'elem> {} 315 | impl<'elem> Sealed for Function<'elem> {} 316 | 317 | impl<'map, 'elem: 'map, T> Sealed for Option where T: FilterArgument<'map, 'elem> {} 318 | 319 | impl<'map, 'elem: 'map, T> Sealed for ValueIter<'map, 'elem, T> where T: FilterArgument<'map, 'elem> {} 320 | 321 | impl<'map, 'elem: 'map, T> Sealed for Option> where 322 | T: FilterArgument<'map, 'elem> 323 | { 324 | } 325 | } 326 | 327 | /// Make a filter function easily and avoid boilerplate. 328 | /// 329 | /// This macro accepts the name of the filter function type, the name of the filter and the create 330 | /// function. 331 | /// 332 | /// The macro generates a type implementing `FilterFunction` with the correct `args()` string 333 | /// derived from the function parameters of the specified create function. The generated 334 | /// `FilterFunction::create()` extracts all parameters from the argument map received from 335 | /// VapourSynth and passes them into the specified create function. 336 | /// 337 | /// The create function should look like: 338 | /// 339 | /// ```ignore 340 | /// fn create<'core>( 341 | /// api: API, 342 | /// core: CoreRef<'core>, 343 | /// /* filter arguments */ 344 | /// ) -> Result + 'core>>, Error> { 345 | /// /* ... */ 346 | /// } 347 | /// ``` 348 | /// 349 | /// All VapourSynth-supported types can be used, as well as `Option` for optional parameters and 350 | /// `ValueIter` for array parameters. Array parameters can be empty. 351 | /// 352 | /// Caveat: the macro doesn't currently allow specifying mutable parameters, so to do that they 353 | /// have to be reassigned to a mutable variable in the function body. This is mainly a problem for 354 | /// array parameters. See how the example below handles it. 355 | /// 356 | /// Another caveat: underscore lifetimes are required for receiving `ValueIter`. 357 | /// 358 | /// # Example 359 | /// ```ignore 360 | /// make_filter_function! { 361 | /// MyFilterFunction, "MyFilter" 362 | /// 363 | /// fn create_my_filter<'core>( 364 | /// _api: API, 365 | /// _core: CoreRef<'core>, 366 | /// int_parameter: i64, 367 | /// some_data: &[u8], 368 | /// optional_parameter: Option, 369 | /// array_parameter: ValueIter<'_, 'core, Node<'core>>, 370 | /// optional_array_parameter: Option>>, 371 | /// ) -> Result + 'core>>, Error> { 372 | /// let mut array_parameter = array_parameter; 373 | /// Ok(Some(Box::new(MyFilter::new(/* ... */)))); 374 | /// } 375 | /// } 376 | /// ``` 377 | #[macro_export] 378 | macro_rules! make_filter_function { 379 | ( 380 | $struct_name:ident, $function_name:tt 381 | 382 | $(#[$attr:meta])* 383 | fn $create_fn_name:ident<$lifetime:tt>( 384 | $api_arg_name:ident : $api_arg_type:ty, 385 | $core_arg_name:ident : $core_arg_type:ty, 386 | $($arg_name:ident : $arg_type:ty),* $(,)* 387 | ) -> $return_type:ty { 388 | $($body:tt)* 389 | } 390 | ) => ( 391 | struct $struct_name { 392 | args: String, 393 | } 394 | 395 | impl $struct_name { 396 | fn new<'core>() -> Self { 397 | let mut args = String::new(); 398 | 399 | $( 400 | // Don't use format!() for better constant propagation. 401 | args += stringify!($arg_name); // TODO: allow using a different name. 402 | args += ":"; 403 | args 404 | += <<$arg_type as $crate::plugins::FilterParameter>::Argument>::type_name(); 405 | 406 | if <$arg_type as $crate::plugins::FilterParameter>::is_array() { 407 | args += "[]"; 408 | } 409 | 410 | if <$arg_type as $crate::plugins::FilterParameter>::is_optional() { 411 | args += ":opt"; 412 | } 413 | 414 | // TODO: allow specifying this. 415 | if <$arg_type as $crate::plugins::FilterParameter>::is_array() { 416 | args += ":empty"; 417 | } 418 | 419 | args += ";"; 420 | )* 421 | 422 | Self { args } 423 | } 424 | } 425 | 426 | impl $crate::plugins::FilterFunction for $struct_name { 427 | #[inline] 428 | fn name(&self) -> &str { 429 | $function_name 430 | } 431 | 432 | #[inline] 433 | fn args(&self) -> &str { 434 | &self.args 435 | } 436 | 437 | #[inline] 438 | fn create<'core>( 439 | &self, 440 | api: API, 441 | core: CoreRef<'core>, 442 | args: &Map<'core>, 443 | ) -> Result + 'core>>, Error> { 444 | $create_fn_name( 445 | api, 446 | core, 447 | $( 448 | <$arg_type as $crate::plugins::FilterParameter>::get_from_map( 449 | args, 450 | stringify!($arg_name), 451 | ) 452 | ),* 453 | ) 454 | } 455 | } 456 | 457 | $(#[$attr])* 458 | fn $create_fn_name<$lifetime>( 459 | $api_arg_name : $api_arg_type, 460 | $core_arg_name : $core_arg_type, 461 | $($arg_name : $arg_type),* 462 | ) -> $return_type { 463 | $($body)* 464 | } 465 | ) 466 | } 467 | -------------------------------------------------------------------------------- /vapoursynth/src/video_info.rs: -------------------------------------------------------------------------------- 1 | //! Video clip formats. 2 | 3 | use std::fmt::Debug; 4 | use std::ops::Deref; 5 | use std::ptr; 6 | use vapoursynth_sys as ffi; 7 | 8 | use crate::format::Format; 9 | use crate::node; 10 | 11 | /// Represents video resolution. 12 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 13 | pub struct Resolution { 14 | /// Width of the clip, greater than 0. 15 | pub width: usize, 16 | 17 | /// Height of the clip, greater than 0. 18 | pub height: usize, 19 | } 20 | 21 | /// Represents video framerate. 22 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 23 | pub struct Framerate { 24 | /// FPS numerator, greater than 0. 25 | pub numerator: u64, 26 | 27 | /// FPS denominator, greater than 0. 28 | pub denominator: u64, 29 | } 30 | 31 | /// Represents a property that can be either constant or variable, like the resolution or the 32 | /// framerate. 33 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 34 | pub enum Property { 35 | /// This property is variable. 36 | Variable, 37 | 38 | /// This property is constant. 39 | Constant(T), 40 | } 41 | 42 | /// Contains information about a video clip. 43 | #[derive(Debug, Copy, Clone)] 44 | pub struct VideoInfo<'core> { 45 | /// Format of the clip. 46 | pub format: Property>, 47 | 48 | /// Framerate of the clip. 49 | pub framerate: Property, 50 | 51 | /// Resolution of the clip. 52 | pub resolution: Property, 53 | 54 | /// Length of the clip, greater than 0. 55 | #[cfg(feature = "gte-vapoursynth-api-32")] 56 | pub num_frames: usize, 57 | 58 | /// Length of the clip. 59 | #[cfg(not(feature = "gte-vapoursynth-api-32"))] 60 | pub num_frames: Property, 61 | 62 | /// The flags of this clip. 63 | pub flags: node::Flags, 64 | } 65 | 66 | impl<'core> VideoInfo<'core> { 67 | /// Creates a `VideoInfo` from a raw pointer. 68 | /// 69 | /// # Safety 70 | /// The caller must ensure `ptr` and the lifetime is valid. 71 | pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSVideoInfo) -> Self { 72 | let info = &*ptr; 73 | 74 | debug_assert!(info.fpsNum >= 0); 75 | debug_assert!(info.fpsDen >= 0); 76 | debug_assert!(info.width >= 0); 77 | debug_assert!(info.height >= 0); 78 | debug_assert!(info.numFrames >= 0); 79 | 80 | let format = if info.format.is_null() { 81 | Property::Variable 82 | } else { 83 | Property::Constant(Format::from_ptr(info.format)) 84 | }; 85 | 86 | let framerate = if info.fpsNum == 0 { 87 | debug_assert!(info.fpsDen == 0); 88 | Property::Variable 89 | } else { 90 | debug_assert!(info.fpsDen != 0); 91 | Property::Constant(Framerate { 92 | numerator: info.fpsNum as _, 93 | denominator: info.fpsDen as _, 94 | }) 95 | }; 96 | 97 | let resolution = if info.width == 0 { 98 | debug_assert!(info.height == 0); 99 | Property::Variable 100 | } else { 101 | debug_assert!(info.height != 0); 102 | Property::Constant(Resolution { 103 | width: info.width as _, 104 | height: info.height as _, 105 | }) 106 | }; 107 | 108 | #[cfg(feature = "gte-vapoursynth-api-32")] 109 | let num_frames = { 110 | debug_assert!(info.numFrames != 0); 111 | info.numFrames as _ 112 | }; 113 | 114 | #[cfg(not(feature = "gte-vapoursynth-api-32"))] 115 | let num_frames = { 116 | if info.numFrames == 0 { 117 | Property::Variable 118 | } else { 119 | Property::Constant(info.numFrames as _) 120 | } 121 | }; 122 | 123 | Self { 124 | format, 125 | framerate, 126 | resolution, 127 | num_frames, 128 | flags: ffi::VSNodeFlags(info.flags).into(), 129 | } 130 | } 131 | 132 | /// Converts the Rust struct into a C struct. 133 | pub(crate) fn ffi_type(self) -> ffi::VSVideoInfo { 134 | let format = match self.format { 135 | Property::Variable => ptr::null(), 136 | Property::Constant(x) => x.deref(), 137 | }; 138 | 139 | let (fps_num, fps_den) = match self.framerate { 140 | Property::Variable => (0, 0), 141 | Property::Constant(Framerate { 142 | numerator, 143 | denominator, 144 | }) => (numerator as i64, denominator as i64), 145 | }; 146 | 147 | let (width, height) = match self.resolution { 148 | Property::Variable => (0, 0), 149 | Property::Constant(Resolution { width, height }) => (width as i32, height as i32), 150 | }; 151 | 152 | #[cfg(feature = "gte-vapoursynth-api-32")] 153 | let num_frames = self.num_frames as i32; 154 | 155 | #[cfg(not(feature = "gte-vapoursynth-api-32"))] 156 | let num_frames = match self.num_frames { 157 | Property::Variable => 0, 158 | Property::Constant(x) => x as i32, 159 | }; 160 | 161 | let flags = self.flags.bits(); 162 | 163 | ffi::VSVideoInfo { 164 | format, 165 | fpsNum: fps_num, 166 | fpsDen: fps_den, 167 | width, 168 | height, 169 | numFrames: num_frames, 170 | flags, 171 | } 172 | } 173 | } 174 | 175 | impl From for Property 176 | where 177 | T: Debug + Clone + Copy + Eq + PartialEq, 178 | { 179 | #[inline] 180 | fn from(x: T) -> Self { 181 | Property::Constant(x) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /vapoursynth/src/vsscript/environment.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::ops::{Deref, DerefMut}; 5 | use std::path::Path; 6 | use std::ptr; 7 | use std::ptr::NonNull; 8 | use vapoursynth_sys as ffi; 9 | 10 | use crate::api::API; 11 | use crate::core::CoreRef; 12 | use crate::map::Map; 13 | use crate::node::Node; 14 | use crate::vsscript::errors::Result; 15 | use crate::vsscript::*; 16 | 17 | use crate::vsscript::VSScriptError; 18 | 19 | /// VSScript file evaluation flags. 20 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 21 | pub enum EvalFlags { 22 | Nothing, 23 | /// The working directory will be changed to the script's directory for the evaluation. 24 | SetWorkingDir, 25 | } 26 | 27 | impl EvalFlags { 28 | #[inline] 29 | fn ffi_type(self) -> ::std::os::raw::c_int { 30 | match self { 31 | EvalFlags::Nothing => 0, 32 | EvalFlags::SetWorkingDir => ffi::VSEvalFlags::efSetWorkingDir as _, 33 | } 34 | } 35 | } 36 | 37 | /// Contains two possible variants of arguments to `Environment::evaluate_script()`. 38 | #[derive(Clone, Copy)] 39 | enum EvaluateScriptArgs<'a> { 40 | /// Evaluate a script contained in the string. 41 | Script(&'a str), 42 | /// Evaluate a script contained in the file. 43 | File(&'a Path, EvalFlags), 44 | } 45 | 46 | /// A wrapper for the VSScript environment. 47 | #[derive(Debug)] 48 | pub struct Environment { 49 | handle: NonNull, 50 | } 51 | 52 | unsafe impl Send for Environment {} 53 | unsafe impl Sync for Environment {} 54 | 55 | impl Drop for Environment { 56 | #[inline] 57 | fn drop(&mut self) { 58 | unsafe { 59 | ffi::vsscript_freeScript(self.handle.as_ptr()); 60 | } 61 | } 62 | } 63 | 64 | impl Environment { 65 | /// Retrieves the VSScript error message. 66 | /// 67 | /// # Safety 68 | /// This function must only be called if an error is present. 69 | #[inline] 70 | unsafe fn error(&self) -> CString { 71 | let message = ffi::vsscript_getError(self.handle.as_ptr()); 72 | CStr::from_ptr(message).to_owned() 73 | } 74 | 75 | /// Creates an empty script environment. 76 | /// 77 | /// Useful if it is necessary to set some variable in the script environment before evaluating 78 | /// any scripts. 79 | pub fn new() -> Result { 80 | maybe_initialize(); 81 | 82 | let mut handle = ptr::null_mut(); 83 | let rv = unsafe { call_vsscript!(ffi::vsscript_createScript(&mut handle)) }; 84 | let environment = Self { 85 | handle: unsafe { NonNull::new_unchecked(handle) }, 86 | }; 87 | 88 | if rv != 0 { 89 | Err(VSScriptError::new(unsafe { environment.error() }).into()) 90 | } else { 91 | Ok(environment) 92 | } 93 | } 94 | 95 | /// Calls `vsscript_evaluateScript()`. 96 | /// 97 | /// `self` is taken by a mutable reference mainly to ensure the atomicity of a call to 98 | /// `vsscript_evaluateScript()` (a function that could produce an error) and the following call 99 | /// to `vsscript_getError()`. If atomicity is not enforced, another thread could perform some 100 | /// operation between these two and clear or change the error message. 101 | fn evaluate_script(&mut self, args: EvaluateScriptArgs) -> Result<()> { 102 | let (script, path, flags) = match args { 103 | EvaluateScriptArgs::Script(script) => (script.to_owned(), None, EvalFlags::Nothing), 104 | EvaluateScriptArgs::File(path, flags) => { 105 | let mut file = File::open(path).map_err(Error::FileOpen)?; 106 | let mut script = String::new(); 107 | file.read_to_string(&mut script).map_err(Error::FileRead)?; 108 | 109 | // vsscript throws an error if it's not valid UTF-8 anyway. 110 | let path = path.to_str().ok_or(Error::PathInvalidUnicode)?; 111 | let path = CString::new(path)?; 112 | 113 | (script, Some(path), flags) 114 | } 115 | }; 116 | 117 | let script = CString::new(script)?; 118 | 119 | let rv = unsafe { 120 | call_vsscript!(ffi::vsscript_evaluateScript( 121 | &mut self.handle.as_ptr(), 122 | script.as_ptr(), 123 | path.as_ref().map(|p| p.as_ptr()).unwrap_or(ptr::null()), 124 | flags.ffi_type(), 125 | )) 126 | }; 127 | 128 | if rv != 0 { 129 | Err(VSScriptError::new(unsafe { self.error() }).into()) 130 | } else { 131 | Ok(()) 132 | } 133 | } 134 | 135 | /// Creates a script environment and evaluates a script contained in a string. 136 | #[inline] 137 | pub fn from_script(script: &str) -> Result { 138 | let mut environment = Self::new()?; 139 | environment.evaluate_script(EvaluateScriptArgs::Script(script))?; 140 | Ok(environment) 141 | } 142 | 143 | /// Creates a script environment and evaluates a script contained in a file. 144 | #[inline] 145 | pub fn from_file>(path: P, flags: EvalFlags) -> Result { 146 | let mut environment = Self::new()?; 147 | environment.evaluate_script(EvaluateScriptArgs::File(path.as_ref(), flags))?; 148 | Ok(environment) 149 | } 150 | 151 | /// Evaluates a script contained in a string. 152 | #[inline] 153 | pub fn eval_script(&mut self, script: &str) -> Result<()> { 154 | self.evaluate_script(EvaluateScriptArgs::Script(script)) 155 | } 156 | 157 | /// Evaluates a script contained in a file. 158 | #[inline] 159 | pub fn eval_file>(&mut self, path: P, flags: EvalFlags) -> Result<()> { 160 | self.evaluate_script(EvaluateScriptArgs::File(path.as_ref(), flags)) 161 | } 162 | 163 | /// Clears the script environment. 164 | #[inline] 165 | pub fn clear(&self) { 166 | unsafe { 167 | ffi::vsscript_clearEnvironment(self.handle.as_ptr()); 168 | } 169 | } 170 | 171 | /// Retrieves a node from the script environment. A node in the script must have been marked 172 | /// for output with the requested index. 173 | #[cfg(all( 174 | not(feature = "gte-vsscript-api-31"), 175 | feature = "vapoursynth-functions" 176 | ))] 177 | #[inline] 178 | pub fn get_output(&self, index: i32) -> Result { 179 | // Node needs the API. 180 | API::get().ok_or(Error::NoAPI)?; 181 | 182 | let node_handle = unsafe { ffi::vsscript_getOutput(self.handle.as_ptr(), index) }; 183 | if node_handle.is_null() { 184 | Err(Error::NoOutput) 185 | } else { 186 | Ok(unsafe { Node::from_ptr(node_handle) }) 187 | } 188 | } 189 | 190 | /// Retrieves a node from the script environment. A node in the script must have been marked 191 | /// for output with the requested index. The second node, if any, contains the alpha clip. 192 | #[cfg(all( 193 | feature = "gte-vsscript-api-31", 194 | any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32") 195 | ))] 196 | #[inline] 197 | pub fn get_output(&self, index: i32) -> Result<(Node, Option)> { 198 | // Node needs the API. 199 | API::get().ok_or(Error::NoAPI)?; 200 | 201 | let mut alpha_handle = ptr::null_mut(); 202 | let node_handle = 203 | unsafe { ffi::vsscript_getOutput2(self.handle.as_ptr(), index, &mut alpha_handle) }; 204 | 205 | if node_handle.is_null() { 206 | return Err(Error::NoOutput); 207 | } 208 | 209 | let node = unsafe { Node::from_ptr(node_handle) }; 210 | let alpha_node = unsafe { alpha_handle.as_mut().map(|p| Node::from_ptr(p)) }; 211 | 212 | Ok((node, alpha_node)) 213 | } 214 | 215 | /// Cancels a node set for output. The node will no longer be available to `get_output()`. 216 | #[inline] 217 | pub fn clear_output(&self, index: i32) -> Result<()> { 218 | let rv = unsafe { ffi::vsscript_clearOutput(self.handle.as_ptr(), index) }; 219 | if rv != 0 { 220 | Err(Error::NoOutput) 221 | } else { 222 | Ok(()) 223 | } 224 | } 225 | 226 | /// Retrieves the VapourSynth core that was created in the script environment. If a VapourSynth 227 | /// core has not been created yet, it will be created now, with the default options. 228 | #[cfg(any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32"))] 229 | pub fn get_core(&self) -> Result { 230 | // CoreRef needs the API. 231 | API::get().ok_or(Error::NoAPI)?; 232 | 233 | let ptr = unsafe { ffi::vsscript_getCore(self.handle.as_ptr()) }; 234 | if ptr.is_null() { 235 | Err(Error::NoCore) 236 | } else { 237 | Ok(unsafe { CoreRef::from_ptr(ptr) }) 238 | } 239 | } 240 | 241 | /// Retrieves a variable from the script environment. 242 | pub fn get_variable(&self, name: &str, map: &mut Map) -> Result<()> { 243 | let name = CString::new(name)?; 244 | let rv = unsafe { 245 | ffi::vsscript_getVariable(self.handle.as_ptr(), name.as_ptr(), map.deref_mut()) 246 | }; 247 | if rv != 0 { 248 | Err(Error::NoSuchVariable) 249 | } else { 250 | Ok(()) 251 | } 252 | } 253 | 254 | /// Sets variables in the script environment. 255 | pub fn set_variables(&self, variables: &Map) -> Result<()> { 256 | let rv = unsafe { ffi::vsscript_setVariable(self.handle.as_ptr(), variables.deref()) }; 257 | if rv != 0 { 258 | Err(Error::NoSuchVariable) 259 | } else { 260 | Ok(()) 261 | } 262 | } 263 | 264 | /// Deletes a variable from the script environment. 265 | pub fn clear_variable(&self, name: &str) -> Result<()> { 266 | let name = CString::new(name)?; 267 | let rv = unsafe { ffi::vsscript_clearVariable(self.handle.as_ptr(), name.as_ptr()) }; 268 | if rv != 0 { 269 | Err(Error::NoSuchVariable) 270 | } else { 271 | Ok(()) 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /vapoursynth/src/vsscript/errors.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CString, NulError}; 2 | use std::{fmt, io}; 3 | 4 | use thiserror::Error; 5 | 6 | /// The error type for `vsscript` operations. 7 | #[derive(Error, Debug)] 8 | pub enum Error { 9 | #[error("Couldn't convert to a CString")] 10 | CStringConversion(#[source] NulError), 11 | #[error("Couldn't open the file")] 12 | FileOpen(#[source] io::Error), 13 | #[error("Couldn't read the file")] 14 | FileRead(#[source] io::Error), 15 | #[error("Path isn't valid Unicode")] 16 | PathInvalidUnicode, 17 | #[error("An error occurred in VSScript")] 18 | VSScript(#[source] VSScriptError), 19 | #[error("There's no such variable")] 20 | NoSuchVariable, 21 | #[error("Couldn't get the core")] 22 | NoCore, 23 | #[error("There's no output on the requested index")] 24 | NoOutput, 25 | #[error("Couldn't get the VapourSynth API")] 26 | NoAPI, 27 | } 28 | 29 | impl From for Error { 30 | #[inline] 31 | fn from(x: NulError) -> Self { 32 | Error::CStringConversion(x) 33 | } 34 | } 35 | 36 | impl From for Error { 37 | #[inline] 38 | fn from(x: VSScriptError) -> Self { 39 | Error::VSScript(x) 40 | } 41 | } 42 | 43 | pub(crate) type Result = std::result::Result; 44 | 45 | /// A container for a VSScript error. 46 | #[derive(Error, Debug)] 47 | pub struct VSScriptError(CString); 48 | 49 | impl fmt::Display for VSScriptError { 50 | #[inline] 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | write!(f, "{}", self.0.to_string_lossy()) 53 | } 54 | } 55 | 56 | impl VSScriptError { 57 | /// Creates a new `VSScriptError` with the given error message. 58 | #[inline] 59 | pub(crate) fn new(message: CString) -> Self { 60 | VSScriptError(message) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /vapoursynth/src/vsscript/mod.rs: -------------------------------------------------------------------------------- 1 | //! VapourSynth script-related things. 2 | 3 | #[cfg(not(feature = "gte-vsscript-api-32"))] 4 | use std::sync::Mutex; 5 | use std::sync::Once; 6 | use vapoursynth_sys as ffi; 7 | 8 | #[cfg(not(feature = "gte-vsscript-api-32"))] 9 | lazy_static! { 10 | static ref FFI_CALL_MUTEX: Mutex<()> = Mutex::new(()); 11 | } 12 | 13 | // Some `vsscript_*` function calls have threading issues. Protect them with a mutex. 14 | // https://github.com/vapoursynth/vapoursynth/issues/367 15 | macro_rules! call_vsscript { 16 | ($call:expr) => {{ 17 | // Fixed in VSScript API 3.2. 18 | // TODO: also not needed when we're running API 3.2 even without a feature. 19 | #[cfg(not(feature = "gte-vsscript-api-32"))] 20 | let _lock = crate::vsscript::FFI_CALL_MUTEX.lock(); 21 | 22 | $call 23 | }}; 24 | } 25 | 26 | /// Ensures `vsscript_init()` has been called at least once. 27 | // TODO: `vsscript_init()` is already thread-safe with `std::call_once()`, maybe this can be done 28 | // differently to remove the thread protection on Rust's side? An idea is to have a special type 29 | // which calls `vsscript_init()` in `new()` and `vsscript_finalize()` in `drop()` and have the rest 30 | // of the API accessible through that type, however that could become somewhat unergonomic with 31 | // having to store its lifetime everywhere and potentially pass it around the threads. 32 | #[inline] 33 | pub(crate) fn maybe_initialize() { 34 | static ONCE: Once = Once::new(); 35 | 36 | ONCE.call_once(|| unsafe { 37 | ffi::vsscript_init(); 38 | 39 | // Verify the VSScript API version. 40 | #[cfg(feature = "gte-vsscript-api-31")] 41 | { 42 | fn split_version(version: i32) -> (i32, i32) { 43 | (version >> 16, version & 0xFFFF) 44 | } 45 | 46 | let vsscript_version = ffi::vsscript_getApiVersion(); 47 | let (major, minor) = split_version(vsscript_version); 48 | let (my_major, my_minor) = split_version(ffi::VSSCRIPT_API_VERSION); 49 | 50 | if my_major != major { 51 | panic!( 52 | "Invalid VSScript major API version (expected: {}, got: {})", 53 | my_major, major 54 | ); 55 | } else if my_minor > minor { 56 | panic!( 57 | "Invalid VSScript minor API version (expected: >= {}, got: {})", 58 | my_minor, minor 59 | ); 60 | } 61 | } 62 | }); 63 | } 64 | 65 | mod errors; 66 | pub use self::errors::{Error, VSScriptError}; 67 | 68 | mod environment; 69 | pub use self::environment::{Environment, EvalFlags}; 70 | -------------------------------------------------------------------------------- /vapoursynth/test-vpy/alpha.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | video = core.std.BlankClip(width = 1920, 4 | height = 1080, 5 | format = vs.RGB24, 6 | length = 100, 7 | fpsnum = 60, 8 | fpsden = 1, 9 | color = [0, 255, 0]) 10 | alpha = core.std.BlankClip(width = 1920, 11 | height = 1080, 12 | format = vs.GRAY8, 13 | length = 100, 14 | fpsnum = 60, 15 | fpsden = 1, 16 | color = [128]) 17 | video.set_output(alpha = alpha) 18 | -------------------------------------------------------------------------------- /vapoursynth/test-vpy/gradient.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | def pixel(color): 5 | return core.std.BlankClip(width = 1, 6 | height = 1, 7 | format = vs.RGB24, 8 | length = 1, 9 | color = color) 10 | 11 | def row(red): 12 | return core.std.StackHorizontal( 13 | [pixel([red, green * 16, 0]) for green in range(16)]) 14 | 15 | clip = core.std.StackVertical([row(red * 16) for red in range(16)]) 16 | # clip = core.resize.Lanczos(clip, format = vs.YUV444P8, matrix_s = '709') 17 | clip.set_output() 18 | -------------------------------------------------------------------------------- /vapoursynth/test-vpy/green.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | video = core.std.BlankClip(width = 1920, 4 | height = 1080, 5 | format = vs.RGB24, 6 | length = 100, 7 | fpsnum = 60, 8 | fpsden = 1, 9 | color = [0, 255, 0]) 10 | # video = core.resize.Bicubic(video, format = vs.YUV444P8, matrix_s = '709') 11 | video.set_output() 12 | -------------------------------------------------------------------------------- /vapoursynth/test-vpy/pixel-formats.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | 4 | def make_clip(format, color): 5 | return core.std.BlankClip(width = 320, 6 | height = 240, 7 | format = format, 8 | length = 100, 9 | fpsnum = 60, 10 | fpsden = 1, 11 | color = color) 12 | 13 | make_clip(vs.YUV420P10, [789, 123, 456]).set_output(0) 14 | make_clip(vs.YUV444PS, [5.0, 42.0, 0.25]).set_output(1) 15 | make_clip(vs.RGBS, [0.125, 10.0, 0.5]).set_output(2) 16 | format = core.register_format(vs.YUV, vs.INTEGER, 17, 0, 0) 17 | make_clip(format.id, [77777, 88888, 99999]).set_output(3) 18 | format = core.register_format(vs.YUV, vs.INTEGER, 32, 0, 0) 19 | make_clip(format.id, [2**32-1, 12345, 65432]).set_output(4) 20 | make_clip(vs.RGBH, [0.0625, 5.0, 0.25]).set_output(5) 21 | -------------------------------------------------------------------------------- /vapoursynth/test-vpy/variable.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | video = core.std.BlankClip(width = 1920, 4 | height = 1080, 5 | format = vs.RGB24, 6 | length = 100, 7 | fpsnum = 60, 8 | fpsden = 1, 9 | color = [0, 255, 0]) 10 | video2 = core.std.BlankClip(width = 1280, 11 | height = 720, 12 | format = vs.GRAY8, 13 | length = 100, 14 | fpsnum = 30, 15 | fpsden = 1, 16 | color = [127]) 17 | # video = core.resize.Bicubic(video, format = vs.YUV444P8, matrix_s = '709') 18 | core.std.Splice([video, video2], mismatch = True).set_output() 19 | --------------------------------------------------------------------------------