initial commit

This commit is contained in:
kageru 2019-05-30 11:06:53 +00:00
commit cf9c084cba
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
47 changed files with 8730 additions and 0 deletions

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "adaptivegrain-rs"
version = "0.1.0"
authors = ["kageru"]
edition = "2018"
[dependencies]
failure = "0.1"
vapoursynth = { path = "vapoursynth" }
[lib]
crate-type = ["cdylib"]

95
src/lib.rs Normal file
View File

@ -0,0 +1,95 @@
#[macro_use]
extern crate failure;
#[macro_use]
extern crate vapoursynth;
use vapoursynth::core::CoreRef;
use vapoursynth::map::Map;
use vapoursynth::video_info::VideoInfo;
use failure::Error;
use vapoursynth::node::Node;
use vapoursynth::plugins::{Filter, FilterArgument, FrameContext, Metadata};
use vapoursynth::api::API;
use vapoursynth::frame::{FrameRef, FrameRefMut};
const PLUGIN_NAME: &str = "adaptivegrain";
const PLUGIN_IDENTIFIER: &str = "moe.kageru.adaptivegrain";
struct AdaptiveGrain<'core> {
source: Node<'core>
}
impl<'core> Filter<'core> for AdaptiveGrain<'core> {
fn video_info(&self, _api: API, _core: CoreRef<'core>) -> Vec<VideoInfo<'core>> {
vec![self.source.info()]
}
fn get_frame_initial(
&self,
_api: API,
_core: CoreRef<'core>,
context: FrameContext,
n: usize,
) -> Result<Option<FrameRef<'core>>, Error> {
self.source.request_frame_filter(context, n);
Ok(None)
}
fn get_frame(
&self,
_api: API,
core: CoreRef<'core>,
context: FrameContext,
n: usize,
) -> Result<FrameRef<'core>, Error> {
let frame = self.source
.get_frame_filter(context, n)
.ok_or_else(|| format_err!("Could not retrieve source frame. This shouldn’t happen."))?;
let average = match frame.props().get::<f64>("PlaneStatsAverage") {
Ok(average) => (average * 256.0) as u8,
Err(_) => panic!(PLUGIN_NAME.to_owned() + ": You need to run std.PlaneStats on the clip before calling this function.")
};
let mut frame = FrameRefMut::copy_of(core, &frame);
for plane in 0..frame.format().plane_count() {
for row in 0..frame.height(plane) {
for pixel in frame.plane_row_mut::<u8>(plane, row) {
*pixel = average;
}
}
}
Ok(frame.into())
}
}
make_filter_function! {
AdaptiveGrainFunction, "AdaptiveGrain"
fn create_adaptivegrain<'core>(
_api: API,
_core: CoreRef<'core>,
clip: Node<'core>,
) -> Result<Option<Box<Filter<'core> + 'core>>, Error> {
Ok(Some(Box::new(AdaptiveGrain { source: clip })))
}
}
export_vapoursynth_plugin! {
Metadata {
identifier: PLUGIN_IDENTIFIER,
namespace: "adg",
name: "Adaptive grain",
read_only: false,
},
[
AdaptiveGrainFunction::new()
]
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

3
vapoursynth-sys/.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "source-files/vapoursynth"]
path = source-files/vapoursynth
url = https://github.com/vapoursynth/vapoursynth.git

View File

@ -0,0 +1,24 @@
### v0.2.1 (16th Jun 2018)
- Added missing plugin-related types
- Silenced some clippy warnings
## v0.2.0 (24th Mar 2018)
- Added support for targetting 32-bit Windows
- Added automatic detection of common Windows VapourSynth library dirs
### v0.1.4 (11th Mar 2018)
- Added an AppVeyor badge
- Added a link to vapoursynth-rs in the README
### v0.1.3 (18th Feb 2018)
- Added a docs.rs badge to the README
- Added CHANGELOG.md
### v0.1.2 (18th Feb 2018)
- Changed the documentation to be hosted at docs.rs
### v0.1.1 (2nd Feb 2018)
- Added new bindings from VSScript API 3.2
## v0.1.0
- Initial release

View File

@ -0,0 +1,59 @@
[package]
name = "vapoursynth-sys"
version = "0.2.2" # remember to update html_root_url
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
description = "Rust bindings for vapoursynth and vsscript."
license = "MIT/Apache-2.0"
build = "build.rs"
readme = "README.md"
documentation = "https://docs.rs/vapoursynth-sys"
repository = "https://github.com/YaLTeR/vapoursynth-rs"
keywords = ["vapoursynth", "vsscript", "video", "bindings"]
categories = ["api-bindings", "external-ffi-bindings", "multimedia::video"]
[badges]
travis-ci = { repository = "YaLTeR/vapoursynth-rs" }
appveyor = { repository = "YaLTeR/vapoursynth-rs" }
[dependencies]
cfg-if = "0.1"
[features]
# Features for enabling higher API versions.
vapoursynth-api-31 = ["gte-vapoursynth-api-31"]
vapoursynth-api-32 = ["gte-vapoursynth-api-31", "gte-vapoursynth-api-32"]
vapoursynth-api-33 = ["gte-vapoursynth-api-31", "gte-vapoursynth-api-32", "gte-vapoursynth-api-33"]
vapoursynth-api-34 = [
"gte-vapoursynth-api-31",
"gte-vapoursynth-api-32",
"gte-vapoursynth-api-33",
"gte-vapoursynth-api-34"
]
vapoursynth-api-35 = [
"gte-vapoursynth-api-31",
"gte-vapoursynth-api-32",
"gte-vapoursynth-api-33",
"gte-vapoursynth-api-34",
"gte-vapoursynth-api-35"
]
vsscript-api-31 = ["gte-vsscript-api-31"]
vsscript-api-32 = ["gte-vsscript-api-31", "gte-vsscript-api-32"]
# Features for linking to certain functions.
vapoursynth-functions = []
vsscript-functions = []
# Utility features, not for outside use.
gte-vapoursynth-api-31 = []
gte-vapoursynth-api-32 = []
gte-vapoursynth-api-33 = []
gte-vapoursynth-api-34 = []
gte-vapoursynth-api-35 = []
gte-vsscript-api-31 = []
gte-vsscript-api-32 = []
[package.metadata.docs.rs]
features = ["vapoursynth-api-35", "vsscript-api-32", "vapoursynth-functions", "vsscript-functions"]

View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
vapoursynth-sys/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

43
vapoursynth-sys/README.md Normal file
View File

@ -0,0 +1,43 @@
# vapoursynth-sys
[![crates.io](https://img.shields.io/crates/v/vapoursynth-sys.svg)](https://crates.io/crates/vapoursynth-sys)
[![Documentation](https://docs.rs/vapoursynth-sys/badge.svg)](https://docs.rs/vapoursynth-sys)
[ChangeLog](https://github.com/YaLTeR/vapoursynth-rs/blob/master/vapoursynth-sys/CHANGELOG.md)
Raw bindings to [VapourSynth](https://github.com/vapoursynth/vapoursynth).
Check out [vapoursynth-rs](https://crates.io/crates/vapoursynth) for a safe Rust wrapper.
## Supported Versions
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:
* `vapoursynth-api-31` for VapourSynth API 3.1
* `vapoursynth-api-32` for VapourSynth API 3.2
* `vapoursynth-api-33` for VapourSynth API 3.3
* `vapoursynth-api-34` for VapourSynth API 3.4
* `vapoursynth-api-35` for VapourSynth API 3.5
* `vsscript-api-31` for VSScript API 3.1
* `vsscript-api-32` for VSScript API 3.2
To enable linking to VapourSynth or VSScript functions, enable the following Cargo features:
* `vapoursynth-functions` for VapourSynth functions (`getVapourSynthAPI()`)
* `vsscript-functions` for VSScript functions (`vsscript_*()`)
## Building
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.
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 `<path to the VapourSynth installation>\sdk\lib64` or `<...>\lib32`, depending on the target bitness.
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.

67
vapoursynth-sys/build.rs Normal file
View File

@ -0,0 +1,67 @@
use std::env;
use std::path::PathBuf;
const LIBRARY_DIR_VARIABLE: &str = "VAPOURSYNTH_LIB_DIR";
fn main() {
// Make sure the build script is re-run if our env variable is changed.
println!("cargo:rerun-if-env-changed={}", LIBRARY_DIR_VARIABLE);
let windows = env::var("TARGET").unwrap().contains("windows");
// Get the default library dir on Windows.
let default_library_dir = if windows {
get_default_library_dir()
} else {
None
};
// Library directory override or the default dir on windows.
if let Some(dir) = env::var(LIBRARY_DIR_VARIABLE).ok().or(default_library_dir) {
println!("cargo:rustc-link-search=native={}", dir);
}
// Handle linking to VapourSynth libs.
if env::var("CARGO_FEATURE_VAPOURSYNTH_FUNCTIONS").is_ok() {
println!("cargo:rustc-link-lib=vapoursynth");
}
if env::var("CARGO_FEATURE_VSSCRIPT_FUNCTIONS").is_ok() {
let vsscript_lib_name = if windows {
"vsscript"
} else {
"vapoursynth-script"
};
println!("cargo:rustc-link-lib={}", vsscript_lib_name);
}
}
// Returns the default library dir on Windows.
// The default dir is where the VapourSynth installer puts the libraries.
fn get_default_library_dir() -> Option<String> {
let host = env::var("HOST").ok()?;
// If the host isn't Windows we don't have %programfiles%.
if !host.contains("windows") {
return None;
}
let programfiles = if host.starts_with("i686") {
env::var("programfiles")
} else {
env::var("programfiles(x86)")
};
let suffix = if env::var("TARGET").ok()?.starts_with("i686") {
"lib32"
} else {
"lib64"
};
let mut path = PathBuf::from(programfiles.ok()?);
path.push("VapourSynth");
path.push("sdk");
path.push(suffix);
path.to_str().map(|s| s.to_owned())
}

View File

@ -0,0 +1,24 @@
#!/bin/sh
bindgen --whitelist-function 'getVapourSynthAPI' \
--whitelist-function 'vsscript.*' \
--whitelist-type 'VSColorFamily' \
--whitelist-type 'VSSampleType' \
--whitelist-type 'VSPresetFormat' \
--whitelist-type 'VSFilterMode' \
--whitelist-type 'VSNodeFlags' \
--whitelist-type 'VSPropTypes' \
--whitelist-type 'VSGetPropErrors' \
--whitelist-type 'VSPropAppendMode' \
--whitelist-type 'VSActivationReason' \
--whitelist-type 'VSMessageType' \
--whitelist-type 'VSEvalFlags' \
--whitelist-type 'VSInitPlugin' \
--blacklist-type '__int64_t' \
--blacklist-type '__uint8_t' \
--bitfield-enum 'VSNodeFlags' \
--rustified-enum 'VS[^N].*' \
--no-layout-tests \
-o bindings.rs \
vapoursynth/include/VSScript.h \
-- -target x86_64-unknown-windows-unknown

View File

@ -0,0 +1 @@
#include "vapoursynth/include/VSScript.h"

View File

@ -0,0 +1,564 @@
use std::os::raw::*;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSFrameRef {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSNodeRef {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSCore {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSPlugin {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSNode {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSFuncRef {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSMap {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSFrameContext {
_unused: [u8; 0],
}
#[cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSColorFamily {
cmGray = 1000000,
cmRGB = 2000000,
cmYUV = 3000000,
cmYCoCg = 4000000,
cmCompat = 9000000,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSSampleType {
stInteger = 0,
stFloat = 1,
}
#[cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSPresetFormat {
pfNone = 0,
pfGray8 = 1000010,
pfGray16 = 1000011,
pfGrayH = 1000012,
pfGrayS = 1000013,
pfYUV420P8 = 3000010,
pfYUV422P8 = 3000011,
pfYUV444P8 = 3000012,
pfYUV410P8 = 3000013,
pfYUV411P8 = 3000014,
pfYUV440P8 = 3000015,
pfYUV420P9 = 3000016,
pfYUV422P9 = 3000017,
pfYUV444P9 = 3000018,
pfYUV420P10 = 3000019,
pfYUV422P10 = 3000020,
pfYUV444P10 = 3000021,
pfYUV420P16 = 3000022,
pfYUV422P16 = 3000023,
pfYUV444P16 = 3000024,
pfYUV444PH = 3000025,
pfYUV444PS = 3000026,
pfYUV420P12 = 3000027,
pfYUV422P12 = 3000028,
pfYUV444P12 = 3000029,
pfYUV420P14 = 3000030,
pfYUV422P14 = 3000031,
pfYUV444P14 = 3000032,
pfRGB24 = 2000010,
pfRGB27 = 2000011,
pfRGB30 = 2000012,
pfRGB48 = 2000013,
pfRGBH = 2000014,
pfRGBS = 2000015,
pfCompatBGR32 = 9000010,
pfCompatYUY2 = 9000011,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSFilterMode {
fmParallel = 100,
fmParallelRequests = 200,
fmUnordered = 300,
fmSerial = 400,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSFormat {
pub name: [c_char; 32usize],
pub id: c_int,
pub colorFamily: c_int,
pub sampleType: c_int,
pub bitsPerSample: c_int,
pub bytesPerSample: c_int,
pub subSamplingW: c_int,
pub subSamplingH: c_int,
pub numPlanes: c_int,
}
pub const VSNodeFlags_nfNoCache: VSNodeFlags = VSNodeFlags(1);
pub const VSNodeFlags_nfIsCache: VSNodeFlags = VSNodeFlags(2);
#[cfg(feature = "gte-vapoursynth-api-33")]
pub const VSNodeFlags_nfMakeLinear: VSNodeFlags = VSNodeFlags(4);
impl ::std::ops::BitOr<VSNodeFlags> for VSNodeFlags {
type Output = Self;
#[inline]
fn bitor(self, other: Self) -> Self {
VSNodeFlags(self.0 | other.0)
}
}
impl ::std::ops::BitOrAssign for VSNodeFlags {
#[inline]
fn bitor_assign(&mut self, rhs: VSNodeFlags) {
self.0 |= rhs.0;
}
}
impl ::std::ops::BitAnd<VSNodeFlags> for VSNodeFlags {
type Output = Self;
#[inline]
fn bitand(self, other: Self) -> Self {
VSNodeFlags(self.0 & other.0)
}
}
impl ::std::ops::BitAndAssign for VSNodeFlags {
#[inline]
fn bitand_assign(&mut self, rhs: VSNodeFlags) {
self.0 &= rhs.0;
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct VSNodeFlags(pub c_int);
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSPropTypes {
ptUnset = 117,
ptInt = 105,
ptFloat = 102,
ptData = 115,
ptNode = 99,
ptFrame = 118,
ptFunction = 109,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSGetPropErrors {
peUnset = 1,
peType = 2,
peIndex = 4,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSPropAppendMode {
paReplace = 0,
paAppend = 1,
paTouch = 2,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSCoreInfo {
pub versionString: *const c_char,
pub core: c_int,
pub api: c_int,
pub numThreads: c_int,
pub maxFramebufferSize: i64,
pub usedFramebufferSize: i64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSVideoInfo {
pub format: *const VSFormat,
pub fpsNum: i64,
pub fpsDen: i64,
pub width: c_int,
pub height: c_int,
pub numFrames: c_int,
pub flags: c_int,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSActivationReason {
arInitial = 0,
arFrameReady = 1,
arAllFramesReady = 2,
arError = -1,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSMessageType {
mtDebug = 0,
mtWarning = 1,
mtCritical = 2,
mtFatal = 3,
}
pub type VSPublicFunction = unsafe extern "system" fn(
in_: *const VSMap,
out: *mut VSMap,
userData: *mut c_void,
core: *mut VSCore,
vsapi: *const VSAPI,
);
pub type VSRegisterFunction = unsafe extern "system" fn(
name: *const c_char,
args: *const c_char,
argsFunc: VSPublicFunction,
functionData: *mut c_void,
plugin: *mut VSPlugin,
);
pub type VSConfigPlugin = unsafe extern "system" fn(
identifier: *const c_char,
defaultNamespace: *const c_char,
name: *const c_char,
apiVersion: c_int,
readonly: c_int,
plugin: *mut VSPlugin,
);
pub type VSInitPlugin = Option<
unsafe extern "system" fn(
configFunc: VSConfigPlugin,
registerFunc: VSRegisterFunction,
plugin: *mut VSPlugin,
),
>;
pub type VSFreeFuncData = Option<unsafe extern "system" fn(userData: *mut c_void)>;
pub type VSFilterInit = unsafe extern "system" fn(
in_: *mut VSMap,
out: *mut VSMap,
instanceData: *mut *mut c_void,
node: *mut VSNode,
core: *mut VSCore,
vsapi: *const VSAPI,
);
pub type VSFilterGetFrame = unsafe extern "system" fn(
n: c_int,
activationReason: c_int,
instanceData: *mut *mut c_void,
frameData: *mut *mut c_void,
frameCtx: *mut VSFrameContext,
core: *mut VSCore,
vsapi: *const VSAPI,
) -> *const VSFrameRef;
pub type VSFilterFree = Option<
unsafe extern "system" fn(instanceData: *mut c_void, core: *mut VSCore, vsapi: *const VSAPI),
>;
pub type VSFrameDoneCallback = Option<
unsafe extern "system" fn(
userData: *mut c_void,
f: *const VSFrameRef,
n: c_int,
arg1: *mut VSNodeRef,
errorMsg: *const c_char,
),
>;
pub type VSMessageHandler =
Option<unsafe extern "system" fn(msgType: c_int, msg: *const c_char, userData: *mut c_void)>;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct VSAPI {
pub createCore: unsafe extern "system" fn(threads: c_int) -> *mut VSCore,
pub freeCore: unsafe extern "system" fn(core: *mut VSCore),
pub getCoreInfo: unsafe extern "system" fn(core: *mut VSCore) -> *const VSCoreInfo,
pub cloneFrameRef: unsafe extern "system" fn(f: *const VSFrameRef) -> *const VSFrameRef,
pub cloneNodeRef: unsafe extern "system" fn(node: *mut VSNodeRef) -> *mut VSNodeRef,
pub cloneFuncRef: unsafe extern "system" fn(f: *mut VSFuncRef) -> *mut VSFuncRef,
pub freeFrame: unsafe extern "system" fn(f: *const VSFrameRef),
pub freeNode: unsafe extern "system" fn(node: *mut VSNodeRef),
pub freeFunc: unsafe extern "system" fn(f: *mut VSFuncRef),
pub newVideoFrame: unsafe extern "system" fn(
format: *const VSFormat,
width: c_int,
height: c_int,
propSrc: *const VSFrameRef,
core: *mut VSCore,
) -> *mut VSFrameRef,
pub copyFrame:
unsafe extern "system" fn(f: *const VSFrameRef, core: *mut VSCore) -> *mut VSFrameRef,
pub copyFrameProps:
unsafe extern "system" fn(src: *const VSFrameRef, dst: *mut VSFrameRef, core: *mut VSCore),
pub registerFunction: unsafe extern "system" fn(
name: *const c_char,
args: *const c_char,
argsFunc: VSPublicFunction,
functionData: *mut c_void,
plugin: *mut VSPlugin,
),
pub getPluginById:
unsafe extern "system" fn(identifier: *const c_char, core: *mut VSCore) -> *mut VSPlugin,
pub getPluginByNs:
unsafe extern "system" fn(ns: *const c_char, core: *mut VSCore) -> *mut VSPlugin,
pub getPlugins: unsafe extern "system" fn(core: *mut VSCore) -> *mut VSMap,
pub getFunctions: unsafe extern "system" fn(plugin: *mut VSPlugin) -> *mut VSMap,
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
pub createFilter: unsafe extern "system" fn(
in_: *const VSMap,
out: *mut VSMap,
name: *const c_char,
init: VSFilterInit,
getFrame: VSFilterGetFrame,
free: VSFilterFree,
filterMode: c_int,
flags: c_int,
instanceData: *mut c_void,
core: *mut VSCore,
),
pub setError: unsafe extern "system" fn(map: *mut VSMap, errorMessage: *const c_char),
pub getError: unsafe extern "system" fn(map: *const VSMap) -> *const c_char,
pub setFilterError:
unsafe extern "system" fn(errorMessage: *const c_char, frameCtx: *mut VSFrameContext),
pub invoke:
unsafe extern "system" fn(plugin: *mut VSPlugin, name: *const c_char, args: *const VSMap)
-> *mut VSMap,
pub getFormatPreset: unsafe extern "system" fn(id: c_int, core: *mut VSCore) -> *const VSFormat,
pub registerFormat: unsafe extern "system" fn(
colorFamily: c_int,
sampleType: c_int,
bitsPerSample: c_int,
subSamplingW: c_int,
subSamplingH: c_int,
core: *mut VSCore,
) -> *const VSFormat,
pub getFrame: unsafe extern "system" fn(
n: c_int,
node: *mut VSNodeRef,
errorMsg: *mut c_char,
bufSize: c_int,
) -> *const VSFrameRef,
pub getFrameAsync: unsafe extern "system" fn(
n: c_int,
node: *mut VSNodeRef,
callback: VSFrameDoneCallback,
userData: *mut c_void,
),
pub getFrameFilter:
unsafe extern "system" fn(n: c_int, node: *mut VSNodeRef, frameCtx: *mut VSFrameContext)
-> *const VSFrameRef,
pub requestFrameFilter:
unsafe extern "system" fn(n: c_int, node: *mut VSNodeRef, frameCtx: *mut VSFrameContext),
pub queryCompletedFrame: unsafe extern "system" fn(
node: *mut *mut VSNodeRef,
n: *mut c_int,
frameCtx: *mut VSFrameContext,
),
pub releaseFrameEarly:
unsafe extern "system" fn(node: *mut VSNodeRef, n: c_int, frameCtx: *mut VSFrameContext),
pub getStride: unsafe extern "system" fn(f: *const VSFrameRef, plane: c_int) -> c_int,
pub getReadPtr: unsafe extern "system" fn(f: *const VSFrameRef, plane: c_int) -> *const u8,
pub getWritePtr: unsafe extern "system" fn(f: *mut VSFrameRef, plane: c_int) -> *mut u8,
pub createFunc: unsafe extern "system" fn(
func: VSPublicFunction,
userData: *mut c_void,
free: VSFreeFuncData,
core: *mut VSCore,
vsapi: *const VSAPI,
) -> *mut VSFuncRef,
pub callFunc: unsafe extern "system" fn(
func: *mut VSFuncRef,
in_: *const VSMap,
out: *mut VSMap,
core: *mut VSCore,
vsapi: *const VSAPI,
),
pub createMap: unsafe extern "system" fn() -> *mut VSMap,
pub freeMap: unsafe extern "system" fn(map: *mut VSMap),
pub clearMap: unsafe extern "system" fn(map: *mut VSMap),
pub getVideoInfo: unsafe extern "system" fn(node: *mut VSNodeRef) -> *const VSVideoInfo,
pub setVideoInfo:
unsafe extern "system" fn(vi: *const VSVideoInfo, numOutputs: c_int, node: *mut VSNode),
pub getFrameFormat: unsafe extern "system" fn(f: *const VSFrameRef) -> *const VSFormat,
pub getFrameWidth: unsafe extern "system" fn(f: *const VSFrameRef, plane: c_int) -> c_int,
pub getFrameHeight: unsafe extern "system" fn(f: *const VSFrameRef, plane: c_int) -> c_int,
pub getFramePropsRO: unsafe extern "system" fn(f: *const VSFrameRef) -> *const VSMap,
pub getFramePropsRW: unsafe extern "system" fn(f: *mut VSFrameRef) -> *mut VSMap,
pub propNumKeys: unsafe extern "system" fn(map: *const VSMap) -> c_int,
pub propGetKey: unsafe extern "system" fn(map: *const VSMap, index: c_int) -> *const c_char,
pub propNumElements: unsafe extern "system" fn(map: *const VSMap, key: *const c_char) -> c_int,
pub propGetType: unsafe extern "system" fn(map: *const VSMap, key: *const c_char) -> c_char,
pub propGetInt: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> i64,
pub propGetFloat: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> f64,
pub propGetData: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> *const c_char,
pub propGetDataSize: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> c_int,
pub propGetNode: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> *mut VSNodeRef,
pub propGetFrame: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> *const VSFrameRef,
pub propGetFunc: unsafe extern "system" fn(
map: *const VSMap,
key: *const c_char,
index: c_int,
error: *mut c_int,
) -> *mut VSFuncRef,
pub propDeleteKey: unsafe extern "system" fn(map: *mut VSMap, key: *const c_char) -> c_int,
pub propSetInt:
unsafe extern "system" fn(map: *mut VSMap, key: *const c_char, i: i64, append: c_int)
-> c_int,
pub propSetFloat:
unsafe extern "system" fn(map: *mut VSMap, key: *const c_char, d: f64, append: c_int)
-> c_int,
pub propSetData: unsafe extern "system" fn(
map: *mut VSMap,
key: *const c_char,
data: *const c_char,
size: c_int,
append: c_int,
) -> c_int,
pub propSetNode: unsafe extern "system" fn(
map: *mut VSMap,
key: *const c_char,
node: *mut VSNodeRef,
append: c_int,
) -> c_int,
pub propSetFrame: unsafe extern "system" fn(
map: *mut VSMap,
key: *const c_char,
f: *const VSFrameRef,
append: c_int,
) -> c_int,
pub propSetFunc: unsafe extern "system" fn(
map: *mut VSMap,
key: *const c_char,
func: *mut VSFuncRef,
append: c_int,
) -> c_int,
pub setMaxCacheSize: unsafe extern "system" fn(bytes: i64, core: *mut VSCore) -> i64,
pub getOutputIndex: unsafe extern "system" fn(frameCtx: *mut VSFrameContext) -> c_int,
pub newVideoFrame2: unsafe extern "system" fn(
format: *const VSFormat,
width: c_int,
height: c_int,
planeSrc: *mut *const VSFrameRef,
planes: *const c_int,
propSrc: *const VSFrameRef,
core: *mut VSCore,
) -> *mut VSFrameRef,
pub setMessageHandler:
unsafe extern "system" fn(handler: VSMessageHandler, userData: *mut c_void),
pub setThreadCount: unsafe extern "system" fn(threads: c_int, core: *mut VSCore) -> c_int,
pub getPluginPath: unsafe extern "system" fn(plugin: *const VSPlugin) -> *const c_char,
#[cfg(feature = "gte-vapoursynth-api-31")]
pub propGetIntArray:
unsafe extern "system" fn(map: *const VSMap, key: *const c_char, error: *mut c_int)
-> *const i64,
#[cfg(feature = "gte-vapoursynth-api-31")]
pub propGetFloatArray:
unsafe extern "system" fn(map: *const VSMap, key: *const c_char, error: *mut c_int)
-> *const f64,
#[cfg(feature = "gte-vapoursynth-api-31")]
pub propSetIntArray:
unsafe extern "system" fn(map: *mut VSMap, key: *const c_char, i: *const i64, size: c_int)
-> c_int,
#[cfg(feature = "gte-vapoursynth-api-31")]
pub propSetFloatArray:
unsafe extern "system" fn(map: *mut VSMap, key: *const c_char, d: *const f64, size: c_int)
-> c_int,
#[cfg(feature = "gte-vapoursynth-api-34")]
pub logMessage: unsafe extern "system" fn(msgType: c_int, msg: *const c_char),
}
#[cfg(feature = "vapoursynth-functions")]
extern "system" {
pub fn getVapourSynthAPI(version: c_int) -> *const VSAPI;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct VSScript {
_unused: [u8; 0],
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VSEvalFlags {
efSetWorkingDir = 1,
}
#[cfg(feature = "vsscript-functions")]
extern "system" {
#[cfg(feature = "gte-vsscript-api-31")]
pub fn vsscript_getApiVersion() -> c_int;
pub fn vsscript_init() -> c_int;
pub fn vsscript_finalize() -> c_int;
pub fn vsscript_evaluateScript(
handle: *mut *mut VSScript,
script: *const c_char,
scriptFilename: *const c_char,
flags: c_int,
) -> c_int;
pub fn vsscript_evaluateFile(
handle: *mut *mut VSScript,
scriptFilename: *const c_char,
flags: c_int,
) -> c_int;
pub fn vsscript_createScript(handle: *mut *mut VSScript) -> c_int;
pub fn vsscript_freeScript(handle: *mut VSScript);
pub fn vsscript_getError(handle: *mut VSScript) -> *const c_char;
pub fn vsscript_getOutput(handle: *mut VSScript, index: c_int) -> *mut VSNodeRef;
#[cfg(feature = "gte-vsscript-api-31")]
pub fn vsscript_getOutput2(
handle: *mut VSScript,
index: c_int,
alpha: *mut *mut VSNodeRef,
) -> *mut VSNodeRef;
pub fn vsscript_clearOutput(handle: *mut VSScript, index: c_int) -> c_int;
pub fn vsscript_getCore(handle: *mut VSScript) -> *mut VSCore;
pub fn vsscript_getVSApi() -> *const VSAPI;
#[cfg(feature = "gte-vsscript-api-32")]
pub fn vsscript_getVSApi2(version: c_int) -> *const VSAPI;
pub fn vsscript_getVariable(
handle: *mut VSScript,
name: *const c_char,
dst: *mut VSMap,
) -> c_int;
pub fn vsscript_setVariable(handle: *mut VSScript, vars: *const VSMap) -> c_int;
pub fn vsscript_clearVariable(handle: *mut VSScript, name: *const c_char) -> c_int;
pub fn vsscript_clearEnvironment(handle: *mut VSScript);
}

View File

@ -0,0 +1,43 @@
//! Raw bindings to [VapourSynth](https://github.com/vapoursynth/vapoursynth).
#![doc(html_root_url = "https://docs.rs/vapoursynth-sys/0.2.2")]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#[macro_use]
extern crate cfg_if;
mod bindings;
pub use bindings::*;
macro_rules! api_version {
($major:expr, $minor:expr) => {
($major << 16) | $minor
};
}
cfg_if! {
if #[cfg(feature="vapoursynth-api-35")] {
pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 5);
} else if #[cfg(feature="vapoursynth-api-34")] {
pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 4);
} else if #[cfg(feature="vapoursynth-api-33")] {
pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 3);
} else if #[cfg(feature="vapoursynth-api-32")] {
pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 2);
} else if #[cfg(feature="vapoursynth-api-31")] {
pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 1);
} else {
pub const VAPOURSYNTH_API_VERSION: i32 = api_version!(3, 0);
}
}
cfg_if! {
if #[cfg(feature="vsscript-api-32")] {
pub const VSSCRIPT_API_VERSION: i32 = api_version!(3, 2);
} else if #[cfg(feature="vsscript-api-31")] {
pub const VSSCRIPT_API_VERSION: i32 = api_version!(3, 1);
} else {
pub const VSSCRIPT_API_VERSION: i32 = api_version!(3, 0);
}
}

24
vapoursynth/CHANGELOG.md Normal file
View File

@ -0,0 +1,24 @@
## v0.2 (16th Jun 2018)
- Added plugin support! That includes:
- `plugins::{Metadata,Filter,FilterFunction}` types and traits for making plugins;
- `export_vapoursynth_plugin!` macro for exporting a VapourSynth plugin;
- `make_filter_function!` macro for making filters without much boilerplate.
- Added a sample plugin in the `sample-filter` folder.
- 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.
- Added `plugin::Plugin` and other relevant APIs for enumerating plugins and invoking their functions.
- Added lifetime parameters to many types to fix soundness issues.
- Split `Frame` into `Frame`, `FrameRef`, `FrameRefMut`.
- Added the `map::Value` trait and generic `Map::{get,get_iter,set,append}()` functions.
- Added format caching in `Frame` to reduce the number of API calls needed.
- Added some convenience `From` impls.
### v0.1.2 (2nd Apr 2018)
- Fixed `Frame::data_row()` returning slices of incorrect rows (using the `plane` value instead of the `row` value).
### v0.1.1 (24th Mar 2018)
- Added support for targetting 32-bit Windows
- Added automatic detection of common Windows VapourSynth library dirs
- Fixed `Frame::data()` and `Frame::data_row()` returning slices of incorrect sizes (too short) for pixel formats with more than 1 byte per pixel
## v0.1.0
- Initial release

101
vapoursynth/Cargo.toml Normal file
View File

@ -0,0 +1,101 @@
[package]
name = "vapoursynth"
version = "0.2.0" # remember to update html_root_url
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
description = "Safe Rust wrapper for VapourSynth and VSScript."
license = "MIT/Apache-2.0"
readme = "README.md"
documentation = "https://docs.rs/vapoursynth"
repository = "https://github.com/YaLTeR/vapoursynth-rs"
keywords = ["vapoursynth", "vsscript", "video", "bindings"]
categories = ["api-bindings", "external-ffi-bindings", "multimedia::video"]
[badges]
travis-ci = { repository = "YaLTeR/vapoursynth-rs" }
appveyor = { repository = "YaLTeR/vapoursynth-rs" }
[dependencies]
bitflags = "1"
failure = "0.1"
failure_derive = "0.1"
half = { version = "1", optional = true }
lazy_static = "1"
vapoursynth-sys = { version = "0.2", path = "../vapoursynth-sys" }
[dev-dependencies]
clap = "2"
lazy_static = "1"
num-rational = { version = "0.1", default-features = false }
[features]
# Enable the half::f16 type to be used for frame pixel data.
f16-pixel-type = ["half"]
# Features for enabling higher API versions.
vapoursynth-api-31 = [
"vapoursynth-sys/vapoursynth-api-31",
"gte-vapoursynth-api-31"
]
vapoursynth-api-32 = [
"vapoursynth-sys/vapoursynth-api-32",
"gte-vapoursynth-api-31",
"gte-vapoursynth-api-32"
]
vapoursynth-api-33 = [
"vapoursynth-sys/vapoursynth-api-33",
"gte-vapoursynth-api-31",
"gte-vapoursynth-api-32",
"gte-vapoursynth-api-33"
]
vapoursynth-api-34 = [
"vapoursynth-sys/vapoursynth-api-34",
"gte-vapoursynth-api-31",
"gte-vapoursynth-api-32",
"gte-vapoursynth-api-33",
"gte-vapoursynth-api-34"
]
vapoursynth-api-35 = [
"vapoursynth-sys/vapoursynth-api-35",
"gte-vapoursynth-api-31",
"gte-vapoursynth-api-32",
"gte-vapoursynth-api-33",
"gte-vapoursynth-api-34",
"gte-vapoursynth-api-35"
]
vsscript-api-31 = [
"vapoursynth-sys/vsscript-api-31",
"gte-vsscript-api-31"
]
vsscript-api-32 = [
"vapoursynth-sys/vsscript-api-32",
"gte-vsscript-api-31",
"gte-vsscript-api-32"
]
# Features for linking to certain functions.
vapoursynth-functions = ["vapoursynth-sys/vapoursynth-functions"]
vsscript-functions = ["vapoursynth-sys/vsscript-functions"]
# Utility features, not for outside use.
gte-vapoursynth-api-31 = []
gte-vapoursynth-api-32 = []
gte-vapoursynth-api-33 = []
gte-vapoursynth-api-34 = []
gte-vapoursynth-api-35 = []
gte-vsscript-api-31 = []
gte-vsscript-api-32 = []
# For development.
#default = ["vapoursynth-api-35", "vsscript-api-32", "vapoursynth-functions", "vsscript-functions"]
[package.metadata.docs.rs]
features = [
"vapoursynth-api-35",
"vsscript-api-32",
"vapoursynth-functions",
"vsscript-functions",
"f16-pixel-type"
]

1
vapoursynth/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
vapoursynth/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

1
vapoursynth/README.md Symbolic link
View File

@ -0,0 +1 @@
../README.md

View File

@ -0,0 +1,202 @@
#![allow(unused)]
#[macro_use]
extern crate failure;
extern crate vapoursynth;
use failure::{err_msg, Error, ResultExt};
use std::env;
use vapoursynth::prelude::*;
fn usage() {
println!(
"Usage:\n\t{} <script.vpy> [frame number]",
env::current_exe()
.ok()
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
.unwrap_or_else(|| "vpy-info".to_owned())
);
}
#[cfg(all(
feature = "vsscript-functions",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
))]
fn print_node_info(node: &Node) {
use std::fmt::Debug;
// Helper function for printing properties.
fn map_or_variable<T, F>(x: &Property<T>, f: F) -> String
where
T: Debug + Clone + Copy + Eq + PartialEq,
F: FnOnce(&T) -> String,
{
match *x {
Property::Variable => "variable".to_owned(),
Property::Constant(ref x) => f(x),
}
}
let info = node.info();
println!(
"Format: {}",
map_or_variable(&info.format, |x| x.name().to_owned())
);
println!(
"Resolution: {}",
map_or_variable(&info.resolution, |x| format!("{}×{}", x.width, x.height))
);
println!(
"Framerate: {}",
map_or_variable(&info.framerate, |x| format!(
"{}/{} ({})",
x.numerator,
x.denominator,
x.numerator as f64 / x.denominator as f64
))
);
#[cfg(feature = "gte-vapoursynth-api-32")]
println!("Frame count: {}", info.num_frames);
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
println!(
"Frame count: {}",
map_or_variable(&info.num_frames, |x| format!("{}", x))
);
}
#[cfg(all(
feature = "vsscript-functions",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
))]
fn run() -> Result<(), Error> {
let filename = env::args()
.nth(1)
.ok_or_else(|| err_msg("The filename argument is missing"))?;
let environment =
vsscript::Environment::from_file(filename, vsscript::EvalFlags::SetWorkingDir)
.context("Couldn't create the VSScript environment")?;
let core = environment
.get_core()
.context("Couldn't get the VapourSynth core")?;
println!("{}", core.info());
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = environment
.get_output(0)
.context("Couldn't get the output at index 0")?;
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (
environment
.get_output(0)
.context("Couldn't get the output at index 0")?,
None::<Node>,
);
print_node_info(&node);
println!();
if let Some(alpha_node) = alpha_node {
println!("Alpha:");
print_node_info(&alpha_node);
} else {
println!("Alpha: No");
}
if let Some(n) = env::args().nth(2) {
let n = n
.parse::<usize>()
.context("Couldn't parse the frame number")?;
if n > i32::max_value() as usize {
bail!("Frame number is too big");
}
let frame = node.get_frame(n).context("Couldn't get the frame")?;
println!();
println!("Frame #{}", n);
let format = frame.format();
println!("Format: {}", format.name());
println!("Plane count: {}", format.plane_count());
let props = frame.props();
let count = props.key_count();
if count > 0 {
println!();
}
for k in 0..count {
let key = props.key(k);
macro_rules! print_value {
($func:ident) => {
println!(
"Property: {} => {:?}",
key,
props.$func(key).unwrap().collect::<Vec<_>>()
)
};
}
match props.value_type(key).unwrap() {
ValueType::Int => print_value!(get_int_iter),
ValueType::Float => print_value!(get_float_iter),
ValueType::Data => print_value!(get_data_iter),
ValueType::Node => print_value!(get_node_iter),
ValueType::Frame => print_value!(get_frame_iter),
ValueType::Function => print_value!(get_function_iter),
}
}
for plane in 0..format.plane_count() {
println!();
println!("Plane #{}", plane);
println!(
"Resolution: {}×{}",
frame.width(plane),
frame.height(plane)
);
println!("Stride: {}", frame.stride(plane));
}
}
Ok(())
}
#[cfg(not(all(
feature = "vsscript-functions",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
)))]
fn run() -> Result<(), Error> {
bail!(
"This example requires the `vsscript-functions` and either `vapoursynth-functions` or \
`vsscript-api-32` features."
)
}
fn main() {
if let Err(err) = run() {
eprintln!("Error: {}", err.cause());
for cause in err.causes().skip(1) {
eprintln!("Caused by: {}", cause);
}
eprintln!("{}", err.backtrace());
usage();
}
}

View File

@ -0,0 +1,893 @@
// This is a mostly drop-in reimplementation of vspipe.
// The main difference is what the errors look like.
#![allow(unused)]
#[macro_use]
extern crate failure;
use failure::{err_msg, Error, ResultExt};
#[cfg(all(
feature = "vsscript-functions",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
))]
mod inner {
#![cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
#![cfg_attr(feature = "cargo-clippy", allow(mutex_atomic))]
extern crate clap;
extern crate num_rational;
extern crate vapoursynth;
use std::cmp;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, stdout, Stdout, Write};
use std::ops::Deref;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Instant;
use self::clap::{App, Arg};
use self::num_rational::Ratio;
use self::vapoursynth::prelude::*;
use super::*;
enum OutputTarget {
File(File),
Stdout(Stdout),
Empty,
}
struct OutputParameters<'core> {
node: Node<'core>,
alpha_node: Option<Node<'core>>,
start_frame: usize,
end_frame: usize,
requests: usize,
y4m: bool,
progress: bool,
}
struct OutputState<'core> {
output_target: OutputTarget,
timecodes_file: Option<File>,
error: Option<(usize, Error)>,
reorder_map: HashMap<usize, (Option<FrameRef<'core>>, Option<FrameRef<'core>>)>,
last_requested_frame: usize,
next_output_frame: usize,
current_timecode: Ratio<i64>,
callbacks_fired: usize,
callbacks_fired_alpha: usize,
last_fps_report_time: Instant,
last_fps_report_frames: usize,
fps: Option<f64>,
}
struct SharedData<'core> {
output_done_pair: (Mutex<bool>, Condvar),
output_parameters: OutputParameters<'core>,
output_state: Mutex<OutputState<'core>>,
}
impl Write for OutputTarget {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
OutputTarget::File(ref mut file) => file.write(buf),
OutputTarget::Stdout(ref mut out) => out.write(buf),
OutputTarget::Empty => Ok(buf.len()),
}
}
fn flush(&mut self) -> io::Result<()> {
match *self {
OutputTarget::File(ref mut file) => file.flush(),
OutputTarget::Stdout(ref mut out) => out.flush(),
OutputTarget::Empty => Ok(()),
}
}
}
fn print_version() -> Result<(), Error> {
let environment = Environment::new().context("Couldn't create the VSScript environment")?;
let core = environment
.get_core()
.context("Couldn't create the VapourSynth core")?;
print!("{}", core.info().version_string);
Ok(())
}
// Parses the --arg arguments in form of key=value.
fn parse_arg(arg: &str) -> Result<(&str, &str), Error> {
arg.find('=')
.map(|index| arg.split_at(index))
.map(|(k, v)| (k, &v[1..]))
.ok_or_else(|| format_err!("No value specified for argument: {}", arg))
}
// Returns "Variable" or the value of the property passed through a function.
fn map_or_variable<T, F>(x: &Property<T>, f: F) -> String
where
T: Debug + Clone + Copy + Eq + PartialEq,
F: FnOnce(&T) -> String,
{
match *x {
Property::Variable => "Variable".to_owned(),
Property::Constant(ref x) => f(x),
}
}
fn print_info(writer: &mut Write, node: &Node, alpha: Option<&Node>) -> Result<(), Error> {
let info = node.info();
writeln!(
writer,
"Width: {}",
map_or_variable(&info.resolution, |x| format!("{}", x.width))
)?;
writeln!(
writer,
"Height: {}",
map_or_variable(&info.resolution, |x| format!("{}", x.height))
)?;
#[cfg(feature = "gte-vapoursynth-api-32")]
writeln!(writer, "Frames: {}", info.num_frames)?;
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
writeln!(
writer,
"Frames: {}",
match info.num_frames {
Property::Variable => "Unknown".to_owned(),
Property::Constant(x) => format!("{}", x),
}
)?;
writeln!(
writer,
"FPS: {}",
map_or_variable(&info.framerate, |x| format!(
"{}/{} ({:.3} fps)",
x.numerator,
x.denominator,
x.numerator as f64 / x.denominator as f64
))
)?;
match info.format {
Property::Variable => writeln!(writer, "Format Name: Variable")?,
Property::Constant(f) => {
writeln!(writer, "Format Name: {}", f.name())?;
writeln!(writer, "Color Family: {}", f.color_family())?;
writeln!(
writer,
"Alpha: {}",
if alpha.is_some() { "Yes" } else { "No" }
)?;
writeln!(writer, "Sample Type: {}", f.sample_type())?;
writeln!(writer, "Bits: {}", f.bits_per_sample())?;
writeln!(writer, "SubSampling W: {}", f.sub_sampling_w())?;
writeln!(writer, "SubSampling H: {}", f.sub_sampling_h())?;
}
}
Ok(())
}
fn print_y4m_header<W: Write>(writer: &mut W, node: &Node) -> Result<(), Error> {
let info = node.info();
if let Property::Constant(format) = info.format {
write!(writer, "YUV4MPEG2 C")?;
match format.color_family() {
ColorFamily::Gray => {
write!(writer, "mono")?;
if format.bits_per_sample() > 8 {
write!(writer, "{}", format.bits_per_sample())?;
}
}
ColorFamily::YUV => {
write!(
writer,
"{}",
match (format.sub_sampling_w(), format.sub_sampling_h()) {
(1, 1) => "420",
(1, 0) => "422",
(0, 0) => "444",
(2, 2) => "410",
(2, 0) => "411",
(0, 1) => "440",
_ => bail!("No y4m identifier exists for the current format"),
}
)?;
if format.bits_per_sample() > 8 && format.sample_type() == SampleType::Integer {
write!(writer, "p{}", format.bits_per_sample())?;
} else if format.sample_type() == SampleType::Float {
write!(
writer,
"p{}",
match format.bits_per_sample() {
16 => "h",
32 => "s",
64 => "d",
_ => unreachable!(),
}
)?;
}
}
_ => bail!("No y4m identifier exists for the current format"),
}
if let Property::Constant(resolution) = info.resolution {
write!(writer, " W{} H{}", resolution.width, resolution.height)?;
} else {
unreachable!();
}
if let Property::Constant(framerate) = info.framerate {
write!(
writer,
" F{}:{}",
framerate.numerator, framerate.denominator
)?;
} else {
unreachable!();
}
#[cfg(feature = "gte-vapoursynth-api-32")]
let num_frames = info.num_frames;
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
let num_frames = {
if let Property::Constant(num_frames) = info.num_frames {
num_frames
} else {
unreachable!();
}
};
write!(writer, " Ip A0:0 XLENGTH={}\n", num_frames)?;
Ok(())
} else {
unreachable!();
}
}
// Checks if the frame is completed, that is, we have the frame and, if needed, its alpha part.
fn is_completed(entry: &(Option<FrameRef>, Option<FrameRef>), have_alpha: bool) -> bool {
entry.0.is_some() && (!have_alpha || entry.1.is_some())
}
fn print_frame<W: Write>(writer: &mut W, frame: &Frame) -> Result<(), Error> {
const RGB_REMAP: [usize; 3] = [1, 2, 0];
let format = frame.format();
#[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
for plane in 0..format.plane_count() {
let plane = if format.color_family() == ColorFamily::RGB {
RGB_REMAP[plane]
} else {
plane
};
if let Ok(data) = frame.data(plane) {
writer.write_all(data)?;
} else {
for row in 0..frame.height(plane) {
writer.write_all(frame.data_row(plane, row))?;
}
}
}
Ok(())
}
fn print_frames<W: Write>(
writer: &mut W,
parameters: &OutputParameters,
frame: &Frame,
alpha_frame: Option<&Frame>,
) -> Result<(), Error> {
if parameters.y4m {
write!(writer, "FRAME\n").context("Couldn't output the frame header")?;
}
print_frame(writer, frame).context("Couldn't output the frame")?;
if let Some(alpha_frame) = alpha_frame {
print_frame(writer, alpha_frame).context("Couldn't output the alpha frame")?;
}
Ok(())
}
fn update_timecodes(frame: &Frame, state: &mut OutputState) -> Result<(), Error> {
let props = frame.props();
let duration_num = props
.get_int("_DurationNum")
.context("Couldn't get the duration numerator")?;
let duration_den = props
.get_int("_DurationDen")
.context("Couldn't get the duration denominator")?;
if duration_den == 0 {
bail!("The duration denominator is zero");
}
state.current_timecode += Ratio::new(duration_num, duration_den);
Ok(())
}
fn frame_done_callback<'core>(
frame: Result<FrameRef<'core>, GetFrameError>,
n: usize,
_node: &Node<'core>,
shared_data: &Arc<SharedData<'core>>,
alpha: bool,
) {
let parameters = &shared_data.output_parameters;
let mut state = shared_data.output_state.lock().unwrap();
// Increase the progress counter.
if !alpha {
state.callbacks_fired += 1;
if parameters.alpha_node.is_none() {
state.callbacks_fired_alpha += 1;
}
} else {
state.callbacks_fired_alpha += 1;
}
// Figure out the FPS.
if parameters.progress {
let current = Instant::now();
let elapsed = current.duration_since(state.last_fps_report_time);
let elapsed_seconds = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9;
if elapsed.as_secs() > 10 {
state.fps = Some(
(state.callbacks_fired - state.last_fps_report_frames) as f64 / elapsed_seconds,
);
state.last_fps_report_time = current;
state.last_fps_report_frames = state.callbacks_fired;
}
}
match frame {
Err(error) => {
if state.error.is_none() {
state.error = Some((
n,
err_msg(error.into_inner().to_string_lossy().into_owned()),
))
}
}
Ok(frame) => {
// Store the frame in the reorder map.
{
let entry = state.reorder_map.entry(n).or_insert((None, None));
if alpha {
entry.1 = Some(frame);
} else {
entry.0 = Some(frame);
}
}
// If we got both a frame and its alpha frame, request one more.
if is_completed(&state.reorder_map[&n], parameters.alpha_node.is_some())
&& state.last_requested_frame < parameters.end_frame
&& state.error.is_none()
{
let shared_data_2 = shared_data.clone();
parameters.node.get_frame_async(
state.last_requested_frame + 1,
move |frame, n, node| {
frame_done_callback(frame, n, &node, &shared_data_2, false)
},
);
if let Some(ref alpha_node) = parameters.alpha_node {
let shared_data_2 = shared_data.clone();
alpha_node.get_frame_async(
state.last_requested_frame + 1,
move |frame, n, node| {
frame_done_callback(frame, n, &node, &shared_data_2, true)
},
);
}
state.last_requested_frame += 1;
}
// Output all completed frames.
while state
.reorder_map
.get(&state.next_output_frame)
.map(|entry| is_completed(entry, parameters.alpha_node.is_some()))
.unwrap_or(false)
{
let next_output_frame = state.next_output_frame;
let (frame, alpha_frame) =
state.reorder_map.remove(&next_output_frame).unwrap();
let frame = frame.unwrap();
if state.error.is_none() {
if let Err(error) = print_frames(
&mut state.output_target,
parameters,
&frame,
alpha_frame.as_ref().map(Deref::deref),
) {
state.error = Some((n, error));
}
}
if state.timecodes_file.is_some() && state.error.is_none() {
let timecode = (*state.current_timecode.numer() as f64 * 1000f64)
/ *state.current_timecode.denom() as f64;
match writeln!(state.timecodes_file.as_mut().unwrap(), "{:.6}", timecode)
.context("Couldn't output the timecode")
{
Err(error) => state.error = Some((n, error.into())),
Ok(()) => if let Err(error) = update_timecodes(&frame, &mut state)
.context("Couldn't update the timecodes")
{
state.error = Some((n, error.into()));
},
}
}
state.next_output_frame += 1;
}
}
}
// Output the progress info.
if parameters.progress {
eprint!(
"Frame: {}/{}",
state.callbacks_fired,
parameters.end_frame - parameters.start_frame + 1
);
if let Some(fps) = state.fps {
eprint!(" ({:.2} fps)", fps);
}
eprint!("\r");
}
// if state.next_output_frame == parameters.end_frame + 1 {
// This condition works with error handling:
let frames_requested = state.last_requested_frame - parameters.start_frame + 1;
if state.callbacks_fired == frames_requested
&& state.callbacks_fired_alpha == frames_requested
{
*shared_data.output_done_pair.0.lock().unwrap() = true;
shared_data.output_done_pair.1.notify_one();
}
}
fn output(
mut output_target: OutputTarget,
mut timecodes_file: Option<File>,
parameters: OutputParameters,
) -> Result<(), Error> {
// Print the y4m header.
if parameters.y4m {
if parameters.alpha_node.is_some() {
bail!("Can't apply y4m headers to a clip with alpha");
}
print_y4m_header(&mut output_target, &parameters.node)
.context("Couldn't write the y4m header")?;
}
// Print the timecodes header.
if let Some(ref mut timecodes_file) = timecodes_file {
writeln!(timecodes_file, "# timecode format v2")?;
}
let initial_requests = cmp::min(
parameters.requests,
parameters.end_frame - parameters.start_frame + 1,
);
let output_done_pair = (Mutex::new(false), Condvar::new());
let output_state = Mutex::new(OutputState {
output_target,
timecodes_file,
error: None,
reorder_map: HashMap::new(),
last_requested_frame: parameters.start_frame + initial_requests - 1,
next_output_frame: 0,
current_timecode: Ratio::from_integer(0),
callbacks_fired: 0,
callbacks_fired_alpha: 0,
last_fps_report_time: Instant::now(),
last_fps_report_frames: 0,
fps: None,
});
let shared_data = Arc::new(SharedData {
output_done_pair,
output_parameters: parameters,
output_state,
});
// Record the start time.
let start_time = Instant::now();
// Start off by requesting some frames.
{
let parameters = &shared_data.output_parameters;
for n in 0..initial_requests {
let shared_data_2 = shared_data.clone();
parameters.node.get_frame_async(n, move |frame, n, node| {
frame_done_callback(frame, n, &node, &shared_data_2, false)
});
if let Some(ref alpha_node) = parameters.alpha_node {
let shared_data_2 = shared_data.clone();
alpha_node.get_frame_async(n, move |frame, n, node| {
frame_done_callback(frame, n, &node, &shared_data_2, true)
});
}
}
}
let &(ref lock, ref cvar) = &shared_data.output_done_pair;
let mut done = lock.lock().unwrap();
while !*done {
done = cvar.wait(done).unwrap();
}
let elapsed = start_time.elapsed();
let elapsed_seconds = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9;
let mut state = shared_data.output_state.lock().unwrap();
eprintln!(
"Output {} frames in {:.2} seconds ({:.2} fps)",
state.next_output_frame,
elapsed_seconds,
state.next_output_frame as f64 / elapsed_seconds
);
if let Some((n, ref msg)) = state.error {
bail!("Failed to retrieve frame {} with error: {}", n, msg);
}
// Flush the output file.
state
.output_target
.flush()
.context("Failed to flush the output file")?;
Ok(())
}
pub fn run() -> Result<(), Error> {
let matches = App::new("vspipe-rs")
.about("A Rust implementation of vspipe")
.author("Ivan M. <yalterz@gmail.com>")
.arg(
Arg::with_name("arg")
.short("a")
.long("arg")
.takes_value(true)
.multiple(true)
.number_of_values(1)
.value_name("key=value")
.display_order(1)
.help("Argument to pass to the script environment")
.long_help(
"Argument to pass to the script environment, \
a key with this name and value (bytes typed) \
will be set in the globals dict",
),
).arg(
Arg::with_name("start")
.short("s")
.long("start")
.takes_value(true)
.value_name("N")
.display_order(2)
.help("First frame to output"),
).arg(
Arg::with_name("end")
.short("e")
.long("end")
.takes_value(true)
.value_name("N")
.display_order(3)
.help("Last frame to output"),
).arg(
Arg::with_name("outputindex")
.short("o")
.long("outputindex")
.takes_value(true)
.value_name("N")
.display_order(4)
.help("Output index"),
).arg(
Arg::with_name("requests")
.short("r")
.long("requests")
.takes_value(true)
.value_name("N")
.display_order(5)
.help("Number of concurrent frame requests"),
).arg(
Arg::with_name("y4m")
.short("y")
.long("y4m")
.help("Add YUV4MPEG headers to output"),
).arg(
Arg::with_name("timecodes")
.short("t")
.long("timecodes")
.takes_value(true)
.value_name("FILE")
.display_order(6)
.help("Write timecodes v2 file"),
).arg(
Arg::with_name("progress")
.short("p")
.long("progress")
.help("Print progress to stderr"),
).arg(
Arg::with_name("info")
.short("i")
.long("info")
.help("Show video info and exit"),
).arg(
Arg::with_name("version")
.short("v")
.long("version")
.help("Show version info and exit")
.conflicts_with_all(&[
"info",
"progress",
"y4m",
"arg",
"start",
"end",
"outputindex",
"requests",
"timecodes",
"script",
"outfile",
]),
).arg(
Arg::with_name("script")
.required_unless("version")
.index(1)
.help("Input .vpy file"),
).arg(
Arg::with_name("outfile")
.required_unless("version")
.index(2)
.help("Output file")
.long_help(
"Output file, use hyphen `-` for stdout \
or dot `.` for suppressing any output",
),
).get_matches();
// Check --version.
if matches.is_present("version") {
return print_version();
}
// Open the output files.
let mut output_target = match matches.value_of_os("outfile").unwrap() {
x if x == OsStr::new(".") => OutputTarget::Empty,
x if x == OsStr::new("-") => OutputTarget::Stdout(stdout()),
path => {
OutputTarget::File(File::create(path).context("Couldn't open the output file")?)
}
};
let timecodes_file = match matches.value_of_os("timecodes") {
Some(path) => {
Some(File::create(path).context("Couldn't open the timecodes output file")?)
}
None => None,
};
// Create a new VSScript environment.
let mut environment =
Environment::new().context("Couldn't create the VSScript environment")?;
// Parse and set the --arg arguments.
if let Some(args) = matches.values_of("arg") {
let mut args_map = OwnedMap::new(API::get().unwrap());
for arg in args.map(parse_arg) {
let (name, value) = arg.context("Couldn't parse an argument")?;
args_map
.append_data(name, value.as_bytes())
.context("Couldn't append an argument value")?;
}
environment
.set_variables(&args_map)
.context("Couldn't set arguments")?;
}
// Start time more similar to vspipe's.
let start_time = Instant::now();
// Evaluate the script.
environment
.eval_file(
matches.value_of("script").unwrap(),
EvalFlags::SetWorkingDir,
).context("Script evaluation failed")?;
// Get the output node.
let output_index = matches
.value_of("outputindex")
.map(str::parse)
.unwrap_or(Ok(0))
.context("Couldn't convert the output index to an integer")?;
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = environment.get_output(output_index).context(format!(
"Couldn't get the output node at index {}",
output_index
))?;
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (
environment.get_output(output_index).context(format!(
"Couldn't get the output node at index {}",
output_index
))?,
None::<Node>,
);
if matches.is_present("info") {
print_info(&mut output_target, &node, alpha_node.as_ref())
.context("Couldn't print info to the output file")?;
output_target
.flush()
.context("Couldn't flush the output file")?;
} else {
let num_frames = {
let info = node.info();
if let Property::Variable = info.format {
bail!("Cannot output clips with varying format");
}
if let Property::Variable = info.resolution {
bail!("Cannot output clips with varying dimensions");
}
if let Property::Variable = info.framerate {
bail!("Cannot output clips with varying framerate");
}
#[cfg(feature = "gte-vapoursynth-api-32")]
let num_frames = info.num_frames;
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
let num_frames = {
match info.num_frames {
Property::Variable => {
// TODO: make it possible?
bail!("Cannot output clips with unknown length");
}
Property::Constant(x) => x,
}
};
num_frames
};
let start_frame = matches
.value_of("start")
.map(str::parse::<i32>)
.unwrap_or(Ok(0))
.context("Couldn't convert the start frame to an integer")?;
let end_frame = matches
.value_of("end")
.map(str::parse::<i32>)
.unwrap_or_else(|| Ok(num_frames as i32 - 1))
.context("Couldn't convert the end frame to an integer")?;
// Check if the input start and end frames make sense.
if start_frame < 0 || end_frame < start_frame || end_frame as usize >= num_frames {
bail!(
"Invalid range of frames to output specified:\n\
first: {}\n\
last: {}\n\
clip length: {}\n\
frames to output: {}",
start_frame,
end_frame,
num_frames,
end_frame
.checked_sub(start_frame)
.and_then(|x| x.checked_add(1))
.map(|x| format!("{}", x))
.unwrap_or_else(|| "<overflow>".to_owned())
);
}
let requests = {
let requests = matches
.value_of("requests")
.map(str::parse::<usize>)
.unwrap_or(Ok(0))
.context("Couldn't convert the request count to an unsigned integer")?;
if requests == 0 {
environment.get_core().unwrap().info().num_threads
} else {
requests
}
};
let y4m = matches.is_present("y4m");
let progress = matches.is_present("progress");
output(
output_target,
timecodes_file,
OutputParameters {
node,
alpha_node,
start_frame: start_frame as usize,
end_frame: end_frame as usize,
requests,
y4m,
progress,
},
).context("Couldn't output the frames")?;
// This is still not a very valid comparison since vspipe does all argument validation
// before it starts the time.
let elapsed = start_time.elapsed();
let elapsed_seconds = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9;
eprintln!("vspipe time: {:.2} seconds", elapsed_seconds);
}
Ok(())
}
}
#[cfg(not(all(
feature = "vsscript-functions",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
)))]
mod inner {
use super::*;
pub fn run() -> Result<(), Error> {
bail!(
"This example requires the `vsscript-functions` and either `vapoursynth-functions` or \
`vsscript-api-32` features."
)
}
}
fn main() {
if let Err(err) = inner::run() {
eprintln!("Error: {}", err.cause());
for cause in err.causes().skip(1) {
eprintln!("Caused by: {}", cause);
}
eprintln!("{}", err.backtrace());
}
}

1003
vapoursynth/src/api.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
//! The pixel component trait.
#[cfg(feature = "f16-pixel-type")]
use half::f16;
use format::{Format, SampleType};
/// A trait for possible pixel components.
///
/// # Safety
/// Implementing this trait allows retrieving slices of pixel data from the frame for the target
/// type, so the target type must be valid for the given format.
pub unsafe trait Component {
/// Returns whether this component is valid for this format.
fn is_valid(format: Format) -> bool;
}
unsafe impl Component for u8 {
#[inline]
fn is_valid(format: Format) -> bool {
format.sample_type() == SampleType::Integer && format.bytes_per_sample() == 1
}
}
unsafe impl Component for u16 {
#[inline]
fn is_valid(format: Format) -> bool {
format.sample_type() == SampleType::Integer && format.bytes_per_sample() == 2
}
}
unsafe impl Component for u32 {
#[inline]
fn is_valid(format: Format) -> bool {
format.sample_type() == SampleType::Integer && format.bytes_per_sample() == 4
}
}
#[cfg(feature = "f16-pixel-type")]
unsafe impl Component for f16 {
#[inline]
fn is_valid(format: Format) -> bool {
format.sample_type() == SampleType::Float && format.bytes_per_sample() == 2
}
}
unsafe impl Component for f32 {
#[inline]
fn is_valid(format: Format) -> bool {
format.sample_type() == SampleType::Float && format.bytes_per_sample() == 4
}
}

184
vapoursynth/src/core.rs Normal file
View File

@ -0,0 +1,184 @@
//! VapourSynth cores.
use std::ffi::{CStr, CString, NulError};
use std::fmt;
use std::marker::PhantomData;
use std::ptr::NonNull;
use vapoursynth_sys as ffi;
use api::API;
use format::{ColorFamily, Format, FormatID, SampleType};
use map::OwnedMap;
use plugin::Plugin;
/// Contains information about a VapourSynth core.
#[derive(Debug, Clone, Copy, Hash)]
pub struct Info {
/// String containing the name of the library, copyright notice, core and API versions.
pub version_string: &'static str,
/// Version of the core.
pub core_version: i32,
/// Version of the API.
pub api_version: i32,
/// Number of worker threads.
pub num_threads: usize,
/// The framebuffer cache will be allowed to grow up to this size (bytes) before memory is
/// aggressively reclaimed.
pub max_framebuffer_size: u64,
/// Current size of the framebuffer cache, in bytes.
pub used_framebuffer_size: u64,
}
/// A reference to a VapourSynth core.
#[derive(Debug, Clone, Copy)]
pub struct CoreRef<'a> {
handle: NonNull<ffi::VSCore>,
_owner: PhantomData<&'a ()>,
}
unsafe impl<'a> Send for CoreRef<'a> {}
unsafe impl<'a> Sync for CoreRef<'a> {}
impl<'a> CoreRef<'a> {
/// Wraps `handle` in a `CoreRef`.
///
/// # Safety
/// The caller must ensure `handle` is valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSCore) -> Self {
Self {
handle: NonNull::new_unchecked(handle),
_owner: PhantomData,
}
}
/// Returns the underlying pointer.
#[inline]
pub(crate) fn ptr(&self) -> *mut ffi::VSCore {
self.handle.as_ptr()
}
/// Returns information about the VapourSynth core.
pub fn info(self) -> Info {
let raw_info = unsafe {
API::get_cached()
.get_core_info(self.handle.as_ptr())
.as_ref()
.unwrap()
};
let version_string = unsafe { CStr::from_ptr(raw_info.versionString).to_str().unwrap() };
debug_assert!(raw_info.numThreads >= 0);
debug_assert!(raw_info.maxFramebufferSize >= 0);
debug_assert!(raw_info.usedFramebufferSize >= 0);
Info {
version_string,
core_version: raw_info.core,
api_version: raw_info.api,
num_threads: raw_info.numThreads as usize,
max_framebuffer_size: raw_info.maxFramebufferSize as u64,
used_framebuffer_size: raw_info.usedFramebufferSize as u64,
}
}
/// Retrieves a registered or preset `Format` by its id. The id can be of a previously
/// registered format, or one of the `PresetFormat`.
#[inline]
pub fn get_format(&self, id: FormatID) -> Option<Format<'a>> {
let ptr = unsafe { API::get_cached().get_format_preset(id.0, self.handle.as_ptr()) };
unsafe { ptr.as_ref().map(|p| Format::from_ptr(p)) }
}
/// Registers a custom video format.
///
/// Returns `None` if an invalid format is described.
///
/// Registering compat formats is not allowed. Only certain privileged built-in filters are
/// allowed to handle compat formats.
///
/// RGB formats are not allowed to be subsampled.
#[inline]
pub fn register_format(
&self,
color_family: ColorFamily,
sample_type: SampleType,
bits_per_sample: u8,
sub_sampling_w: u8,
sub_sampling_h: u8,
) -> Option<Format<'a>> {
unsafe {
API::get_cached()
.register_format(
color_family.into(),
sample_type.into(),
i32::from(bits_per_sample),
i32::from(sub_sampling_w),
i32::from(sub_sampling_h),
self.handle.as_ptr(),
).as_ref()
.map(|p| Format::from_ptr(p))
}
}
/// Returns a plugin with the given identifier.
#[inline]
pub fn get_plugin_by_id(&self, id: &str) -> Result<Option<Plugin>, NulError> {
let id = CString::new(id)?;
let ptr = unsafe { API::get_cached().get_plugin_by_id(id.as_ptr(), self.handle.as_ptr()) };
if ptr.is_null() {
Ok(None)
} else {
Ok(Some(unsafe { Plugin::from_ptr(ptr) }))
}
}
/// Returns a plugin with the given namespace.
///
/// `get_plugin_by_id()` should be used instead.
#[inline]
pub fn get_plugin_by_namespace(&self, namespace: &str) -> Result<Option<Plugin>, NulError> {
let namespace = CString::new(namespace)?;
let ptr =
unsafe { API::get_cached().get_plugin_by_ns(namespace.as_ptr(), self.handle.as_ptr()) };
if ptr.is_null() {
Ok(None)
} else {
Ok(Some(unsafe { Plugin::from_ptr(ptr) }))
}
}
/// Returns a map containing a list of all loaded plugins.
///
/// Keys: meaningless unique strings;
///
/// Values: namespace, identifier, and full name, separated by semicolons.
// TODO: parse the values on the crate side and return a nice struct.
#[inline]
pub fn plugins(&self) -> OwnedMap {
unsafe { OwnedMap::from_ptr(API::get_cached().get_plugins(self.handle.as_ptr())) }
}
}
impl fmt::Display for Info {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.version_string)?;
writeln!(f, "Worker threads: {}", self.num_threads)?;
writeln!(
f,
"Max framebuffer cache size: {}",
self.max_framebuffer_size
)?;
writeln!(
f,
"Current framebuffer cache size: {}",
self.used_framebuffer_size
)
}
}

256
vapoursynth/src/format.rs Normal file
View File

@ -0,0 +1,256 @@
//! VapourSynth frame formats.
use std::ffi::CStr;
use std::fmt::{self, Display};
use std::marker::PhantomData;
use std::ops::Deref;
use vapoursynth_sys as ffi;
/// Contains information about a video format.
#[derive(Debug, Clone, Copy)]
pub struct Format<'core> {
handle: &'core ffi::VSFormat,
}
/// Preset VapourSynth formats.
///
/// The presets suffixed with H and S have floating point sample type. The H and S suffixes stand
/// for half precision and single precision, respectively.
///
/// The compat formats are the only packed formats in VapourSynth. Everything else is planar. They
/// exist for compatibility with Avisynth plugins. They are not to be implemented in native
/// VapourSynth plugins.
#[cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]
#[repr(i32)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum PresetFormat {
Gray8 = 1000010,
Gray16 = 1000011,
GrayH = 1000012,
GrayS = 1000013,
YUV420P8 = 3000010,
YUV422P8 = 3000011,
YUV444P8 = 3000012,
YUV410P8 = 3000013,
YUV411P8 = 3000014,
YUV440P8 = 3000015,
YUV420P9 = 3000016,
YUV422P9 = 3000017,
YUV444P9 = 3000018,
YUV420P10 = 3000019,
YUV422P10 = 3000020,
YUV444P10 = 3000021,
YUV420P16 = 3000022,
YUV422P16 = 3000023,
YUV444P16 = 3000024,
YUV444PH = 3000025,
YUV444PS = 3000026,
YUV420P12 = 3000027,
YUV422P12 = 3000028,
YUV444P12 = 3000029,
YUV420P14 = 3000030,
YUV422P14 = 3000031,
YUV444P14 = 3000032,
RGB24 = 2000010,
RGB27 = 2000011,
RGB30 = 2000012,
RGB48 = 2000013,
RGBH = 2000014,
RGBS = 2000015,
CompatBGR32 = 9000010,
CompatYUY2 = 9000011,
}
/// Format color families.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ColorFamily {
Gray,
RGB,
YUV,
YCoCg,
Compat,
}
/// Format sample types.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SampleType {
Integer,
Float,
}
/// A unique format identifier.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FormatID(pub(crate) i32);
impl<'core> PartialEq for Format<'core> {
#[inline]
fn eq(&self, other: &Format<'core>) -> bool {
self.id() == other.id()
}
}
impl<'core> Eq for Format<'core> {}
#[doc(hidden)]
impl<'core> Deref for Format<'core> {
type Target = ffi::VSFormat;
// Technically this should return `&'core`.
#[inline]
fn deref(&self) -> &Self::Target {
self.handle
}
}
impl<'core> Format<'core> {
/// Wraps a raw pointer in a `Format`.
///
/// # Safety
/// The caller must ensure `ptr` and the lifetime is valid.
#[inline]
pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSFormat) -> Self {
Self { handle: &*ptr }
}
/// Gets the unique identifier of this format.
#[inline]
pub fn id(self) -> FormatID {
FormatID(self.handle.id)
}
/// Gets the printable name of this format.
#[inline]
pub fn name(self) -> &'core str {
unsafe { CStr::from_ptr(&self.handle.name as _).to_str().unwrap() }
}
/// Gets the number of planes of this format.
#[inline]
pub fn plane_count(self) -> usize {
let plane_count = self.handle.numPlanes;
debug_assert!(plane_count >= 0);
plane_count as usize
}
/// Gets the color family of this format.
#[inline]
pub fn color_family(self) -> ColorFamily {
match self.handle.colorFamily {
x if x == ffi::VSColorFamily::cmGray as i32 => ColorFamily::Gray,
x if x == ffi::VSColorFamily::cmRGB as i32 => ColorFamily::RGB,
x if x == ffi::VSColorFamily::cmYUV as i32 => ColorFamily::YUV,
x if x == ffi::VSColorFamily::cmYCoCg as i32 => ColorFamily::YCoCg,
x if x == ffi::VSColorFamily::cmCompat as i32 => ColorFamily::Compat,
_ => unreachable!(),
}
}
/// Gets the sample type of this format.
#[inline]
pub fn sample_type(self) -> SampleType {
match self.handle.sampleType {
x if x == ffi::VSSampleType::stInteger as i32 => SampleType::Integer,
x if x == ffi::VSSampleType::stFloat as i32 => SampleType::Float,
_ => unreachable!(),
}
}
/// Gets the number of significant bits per sample.
#[inline]
pub fn bits_per_sample(self) -> u8 {
let rv = self.handle.bitsPerSample;
debug_assert!(rv >= 0 && rv <= i32::from(u8::max_value()));
rv as u8
}
/// Gets the number of bytes needed for a sample. This is always a power of 2 and the smallest
/// possible that can fit the number of bits used per sample.
#[inline]
pub fn bytes_per_sample(self) -> u8 {
let rv = self.handle.bytesPerSample;
debug_assert!(rv >= 0 && rv <= i32::from(u8::max_value()));
rv as u8
}
/// log2 subsampling factor, applied to second and third plane.
#[inline]
pub fn sub_sampling_w(self) -> u8 {
let rv = self.handle.subSamplingW;
debug_assert!(rv >= 0 && rv <= i32::from(u8::max_value()));
rv as u8
}
/// log2 subsampling factor, applied to second and third plane.
#[inline]
pub fn sub_sampling_h(self) -> u8 {
let rv = self.handle.subSamplingH;
debug_assert!(rv >= 0 && rv <= i32::from(u8::max_value()));
rv as u8
}
}
impl From<PresetFormat> for FormatID {
fn from(x: PresetFormat) -> Self {
FormatID(x as i32)
}
}
#[doc(hidden)]
impl From<ColorFamily> for ffi::VSColorFamily {
#[inline]
fn from(x: ColorFamily) -> Self {
match x {
ColorFamily::Gray => ffi::VSColorFamily::cmGray,
ColorFamily::RGB => ffi::VSColorFamily::cmRGB,
ColorFamily::YUV => ffi::VSColorFamily::cmYUV,
ColorFamily::YCoCg => ffi::VSColorFamily::cmYCoCg,
ColorFamily::Compat => ffi::VSColorFamily::cmCompat,
}
}
}
#[doc(hidden)]
impl From<SampleType> for ffi::VSSampleType {
#[inline]
fn from(x: SampleType) -> Self {
match x {
SampleType::Integer => ffi::VSSampleType::stInteger,
SampleType::Float => ffi::VSSampleType::stFloat,
}
}
}
impl Display for ColorFamily {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"{}",
match *self {
ColorFamily::Gray => "Gray",
ColorFamily::RGB => "RGB",
ColorFamily::YUV => "YUV",
ColorFamily::YCoCg => "YCoCg",
ColorFamily::Compat => "Compat",
}
)
}
}
impl Display for SampleType {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"{}",
match *self {
SampleType::Integer => "Integer",
SampleType::Float => "Float",
}
)
}
}
impl From<i32> for FormatID {
fn from(x: i32) -> Self {
FormatID(x)
}
}

500
vapoursynth/src/frame.rs Normal file
View File

@ -0,0 +1,500 @@
//! VapourSynth frames.
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr::{self, NonNull};
use std::{mem, slice};
use vapoursynth_sys as ffi;
use api::API;
use component::Component;
use core::CoreRef;
use format::Format;
use map::{MapRef, MapRefMut};
use video_info::Resolution;
/// An error indicating that the frame data has non-zero padding.
#[derive(Fail, Debug, Clone, Copy, Eq, PartialEq)]
#[fail(display = "Frame data has non-zero padding: {}", _0)]
pub struct NonZeroPadding(usize);
/// One frame of a clip.
// This type is intended to be publicly used only in reference form.
#[derive(Debug)]
pub struct Frame<'core> {
// The actual mutability of this depends on whether it's accessed via `&Frame` or `&mut Frame`.
handle: NonNull<ffi::VSFrameRef>,
// The cached frame format for fast access.
format: Format<'core>,
_owner: PhantomData<&'core ()>,
}
/// A reference to a ref-counted frame.
#[derive(Debug)]
pub struct FrameRef<'core> {
// Only immutable references to this are allowed.
frame: Frame<'core>,
}
/// A reference to a mutable frame.
#[derive(Debug)]
pub struct FrameRefMut<'core> {
// Both mutable and immutable references to this are allowed.
frame: Frame<'core>,
}
unsafe impl<'core> Send for Frame<'core> {}
unsafe impl<'core> Sync for Frame<'core> {}
#[doc(hidden)]
impl<'core> Deref for Frame<'core> {
type Target = ffi::VSFrameRef;
// Technically this should return `&'core`.
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { self.handle.as_ref() }
}
}
#[doc(hidden)]
impl<'core> DerefMut for Frame<'core> {
// Technically this should return `&'core`.
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.handle.as_mut() }
}
}
impl<'core> Drop for Frame<'core> {
#[inline]
fn drop(&mut self) {
unsafe {
API::get_cached().free_frame(&self);
}
}
}
impl<'core> Clone for FrameRef<'core> {
#[inline]
fn clone(&self) -> Self {
unsafe {
let handle = API::get_cached().clone_frame(self);
Self {
frame: Frame::from_ptr(handle),
}
}
}
}
impl<'core> Deref for FrameRef<'core> {
type Target = Frame<'core>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.frame
}
}
impl<'core> Deref for FrameRefMut<'core> {
type Target = Frame<'core>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.frame
}
}
impl<'core> DerefMut for FrameRefMut<'core> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.frame
}
}
impl<'core> FrameRef<'core> {
/// Wraps `handle` in a `FrameRef`.
///
/// # Safety
/// The caller must ensure `handle` and the lifetime is valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *const ffi::VSFrameRef) -> Self {
Self {
frame: Frame::from_ptr(handle),
}
}
}
impl<'core> FrameRefMut<'core> {
/// Wraps `handle` in a `FrameRefMut`.
///
/// # Safety
/// The caller must ensure `handle` and the lifetime is valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSFrameRef) -> Self {
Self {
frame: Frame::from_ptr(handle),
}
}
/// Creates a copy of the given frame.
///
/// The plane data is copy-on-write, so this isn't very expensive by itself.
///
/// Judging by the underlying implementation, it seems that any valid `core` can be used.
#[inline]
pub fn copy_of(core: CoreRef, frame: &Frame<'core>) -> Self {
Self {
frame: unsafe { Frame::from_ptr(API::get_cached().copy_frame(frame, core.ptr())) },
}
}
/// Creates a new frame with uninitialized plane data.
///
/// Optionally copies the frame properties from the provided `prop_src` frame.
///
/// # Safety
/// The returned frame contains uninitialized plane data. This should be handled carefully. See
/// the docs for `std::mem::uninitialized()` for more information.
///
/// # Panics
/// Panics if the given resolution has components that don't fit into an `i32`.
#[inline]
pub unsafe fn new_uninitialized(
core: CoreRef<'core>,
prop_src: Option<&Frame<'core>>,
format: Format<'core>,
resolution: Resolution,
) -> Self {
assert!(resolution.width <= i32::max_value() as usize);
assert!(resolution.height <= i32::max_value() as usize);
Self {
frame: unsafe {
Frame::from_ptr(API::get_cached().new_video_frame(
&format,
resolution.width as i32,
resolution.height as i32,
prop_src.map(|f| f.deref() as _).unwrap_or(ptr::null()),
core.ptr(),
))
},
}
}
}
impl<'core> From<FrameRefMut<'core>> for FrameRef<'core> {
#[inline]
fn from(x: FrameRefMut<'core>) -> Self {
Self { frame: x.frame }
}
}
impl<'core> Frame<'core> {
/// Converts a pointer to a frame to a reference.
///
/// # Safety
/// The caller needs to ensure the pointer and the lifetime is valid, and that the resulting
/// `Frame` gets put into `FrameRef` or `FrameRefMut` according to the input pointer
/// mutability.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *const ffi::VSFrameRef) -> Self {
Self {
handle: NonNull::new_unchecked(handle as *mut ffi::VSFrameRef),
format: unsafe {
let ptr = API::get_cached().get_frame_format(&*handle);
Format::from_ptr(ptr)
},
_owner: PhantomData,
}
}
/// Returns the frame format.
#[inline]
pub fn format(&self) -> Format<'core> {
self.format
}
/// Returns the width of a plane, in pixels.
///
/// The width depends on the plane number because of the possible chroma subsampling.
///
/// # Panics
/// Panics if `plane >= format().plane_count()`.
#[inline]
pub fn width(&self, plane: usize) -> usize {
assert!(plane < self.format().plane_count());
unsafe { API::get_cached().get_frame_width(self, plane as i32) as usize }
}
/// Returns the height of a plane, in pixels.
///
/// The height depends on the plane number because of the possible chroma subsampling.
///
/// # Panics
/// Panics if `plane >= format().plane_count()`.
#[inline]
pub fn height(&self, plane: usize) -> usize {
assert!(plane < self.format().plane_count());
unsafe { API::get_cached().get_frame_height(self, plane as i32) as usize }
}
/// Returns the resolution of a plane.
///
/// The resolution depends on the plane number because of the possible chroma subsampling.
///
/// # Panics
/// Panics if `plane` is invalid for this frame.
#[inline]
pub fn resolution(&self, plane: usize) -> Resolution {
assert!(plane < self.format().plane_count());
Resolution {
width: self.width(plane),
height: self.height(plane),
}
}
/// Returns the distance in bytes between two consecutive lines of a plane.
///
/// # Panics
/// Panics if `plane >= format().plane_count()`.
#[inline]
pub fn stride(&self, plane: usize) -> usize {
assert!(plane < self.format().plane_count());
unsafe { API::get_cached().get_frame_stride(self, plane as i32) as usize }
}
/// Returns a slice of a plane's pixel row.
///
/// # Panics
/// Panics if the requested plane, row or component type is invalid.
#[inline]
pub fn plane_row<T: Component>(&self, plane: usize, row: usize) -> &[T] {
assert!(plane < self.format().plane_count());
assert!(row < self.height(plane));
assert!(T::is_valid(self.format()));
let stride = self.stride(plane);
let ptr = self.data_ptr(plane);
let offset = stride * row;
assert!(offset <= isize::max_value() as usize);
let offset = offset as isize;
let row_ptr = unsafe { ptr.offset(offset) };
let width = self.width(plane);
unsafe { slice::from_raw_parts(row_ptr as *const T, width) }
}
/// Returns a mutable slice of a plane's pixel row.
///
/// # Panics
/// Panics if the requested plane, row or component type is invalid.
#[inline]
pub fn plane_row_mut<T: Component>(&mut self, plane: usize, row: usize) -> &mut [T] {
assert!(plane < self.format().plane_count());
assert!(row < self.height(plane));
assert!(T::is_valid(self.format()));
let stride = self.stride(plane);
let ptr = self.data_ptr_mut(plane);
let offset = stride * row;
assert!(offset <= isize::max_value() as usize);
let offset = offset as isize;
let row_ptr = unsafe { ptr.offset(offset) };
let width = self.width(plane);
unsafe { slice::from_raw_parts_mut(row_ptr as *mut T, width) }
}
/// Returns a slice of the plane's pixels.
///
/// The length of the returned slice is `height() * width()`. If the pixel data has non-zero
/// padding (that is, `stride()` is larger than `width()`), an error is returned, since
/// returning the data slice would open access to uninitialized bytes.
///
/// # Panics
/// Panics if the requested plane or component type is invalid.
pub fn plane<T: Component>(&self, plane: usize) -> Result<&[T], NonZeroPadding> {
assert!(plane < self.format().plane_count());
assert!(T::is_valid(self.format()));
let stride = self.stride(plane);
let width_in_bytes = self.width(plane) * usize::from(self.format().bytes_per_sample());
if stride != width_in_bytes {
return Err(NonZeroPadding(stride - width_in_bytes));
}
let height = self.height(plane);
let length = height * self.width(plane);
let ptr = self.data_ptr(plane);
Ok(unsafe { slice::from_raw_parts(ptr as *const T, length) })
}
/// Returns a mutable slice of the plane's pixels.
///
/// The length of the returned slice is `height() * width()`. If the pixel data has non-zero
/// padding (that is, `stride()` is larger than `width()`), an error is returned, since
/// returning the data slice would open access to uninitialized bytes.
///
/// # Panics
/// Panics if the requested plane or component type is invalid.
pub fn plane_mut<T: Component>(&mut self, plane: usize) -> Result<&mut [T], NonZeroPadding> {
assert!(plane < self.format().plane_count());
assert!(T::is_valid(self.format()));
let stride = self.stride(plane);
let width_in_bytes = self.width(plane) * usize::from(self.format().bytes_per_sample());
if stride != width_in_bytes {
return Err(NonZeroPadding(stride - width_in_bytes));
}
let height = self.height(plane);
let length = height * self.width(plane);
let ptr = self.data_ptr_mut(plane);
Ok(unsafe { slice::from_raw_parts_mut(ptr as *mut T, length) })
}
/// Returns a pointer to the plane's pixels.
///
/// The pointer points to an array with a length of `height() * stride()` and is valid for as
/// long as the frame is alive.
///
/// # Panics
/// Panics if `plane >= format().plane_count()`.
#[inline]
pub fn data_ptr(&self, plane: usize) -> *const u8 {
assert!(plane < self.format().plane_count());
unsafe { API::get_cached().get_frame_read_ptr(self, plane as i32) }
}
/// Returns a mutable pointer to the plane's pixels.
///
/// The pointer points to an array with a length of `height() * stride()` and is valid for as
/// long as the frame is alive.
///
/// # Panics
/// Panics if `plane >= format().plane_count()`.
#[inline]
pub fn data_ptr_mut(&mut self, plane: usize) -> *mut u8 {
assert!(plane < self.format().plane_count());
unsafe { API::get_cached().get_frame_write_ptr(self, plane as i32) }
}
/// Returns a slice of a plane's pixel row.
///
/// The length of the returned slice is equal to `width() * format().bytes_per_sample()`.
///
/// # Panics
/// Panics if `plane >= format().plane_count()` or if `row >= height()`.
pub fn data_row(&self, plane: usize, row: usize) -> &[u8] {
assert!(plane < self.format().plane_count());
assert!(row < self.height(plane));
let stride = self.stride(plane);
let ptr = self.data_ptr(plane);
let offset = stride * row;
assert!(offset <= isize::max_value() as usize);
let offset = offset as isize;
let row_ptr = unsafe { ptr.offset(offset) };
let width = self.width(plane) * usize::from(self.format().bytes_per_sample());
unsafe { slice::from_raw_parts(row_ptr, width) }
}
/// Returns a mutable slice of a plane's pixel row.
///
/// The length of the returned slice is equal to `width() * format().bytes_per_sample()`.
///
/// # Panics
/// Panics if `plane >= format().plane_count()` or if `row >= height()`.
pub fn data_row_mut(&mut self, plane: usize, row: usize) -> &mut [u8] {
assert!(plane < self.format().plane_count());
assert!(row < self.height(plane));
let stride = self.stride(plane);
let ptr = self.data_ptr_mut(plane);
let offset = stride * row;
assert!(offset <= isize::max_value() as usize);
let offset = offset as isize;
let row_ptr = unsafe { ptr.offset(offset) };
let width = self.width(plane) * usize::from(self.format().bytes_per_sample());
unsafe { slice::from_raw_parts_mut(row_ptr, width) }
}
/// Returns a slice of the plane's pixels.
///
/// The length of the returned slice is `height() * width() * format().bytes_per_sample()`. If
/// the pixel data has non-zero padding (that is, `stride()` is larger than `width()`), an
/// error is returned, since returning the data slice would open access to uninitialized bytes.
///
/// # Panics
/// Panics if `plane >= format().plane_count()` or if `row >= height()`.
pub fn data(&self, plane: usize) -> Result<&[u8], NonZeroPadding> {
assert!(plane < self.format().plane_count());
let stride = self.stride(plane);
let width = self.width(plane) * usize::from(self.format().bytes_per_sample());
if stride != width {
return Err(NonZeroPadding(stride - width));
}
let height = self.height(plane);
let length = height * stride;
let ptr = self.data_ptr(plane);
Ok(unsafe { slice::from_raw_parts(ptr, length) })
}
/// Returns a mutable slice of the plane's pixels.
///
/// The length of the returned slice is `height() * width() * format().bytes_per_sample()`. If
/// the pixel data has non-zero padding (that is, `stride()` is larger than `width()`), an
/// error is returned, since returning the data slice would open access to uninitialized bytes.
///
/// # Panics
/// Panics if `plane >= format().plane_count()` or if `row >= height()`.
pub fn data_mut(&mut self, plane: usize) -> Result<&mut [u8], NonZeroPadding> {
assert!(plane < self.format().plane_count());
let stride = self.stride(plane);
let width = self.width(plane) * usize::from(self.format().bytes_per_sample());
if stride != width {
return Err(NonZeroPadding(stride - width));
}
let height = self.height(plane);
let length = height * stride;
let ptr = self.data_ptr_mut(plane);
Ok(unsafe { slice::from_raw_parts_mut(ptr, length) })
}
/// Returns a map of frame's properties.
#[inline]
pub fn props(&self) -> MapRef {
unsafe { MapRef::from_ptr(API::get_cached().get_frame_props_ro(self)) }
}
/// Returns a mutable map of frame's properties.
#[inline]
pub fn props_mut(&mut self) -> MapRefMut {
unsafe { MapRefMut::from_ptr(API::get_cached().get_frame_props_rw(self)) }
}
}

123
vapoursynth/src/function.rs Normal file
View File

@ -0,0 +1,123 @@
//! VapourSynth callable functions.
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_void;
use std::ptr::NonNull;
use std::{mem, panic, process};
use vapoursynth_sys as ffi;
use api::API;
use core::CoreRef;
use map::{Map, MapRef, MapRefMut};
/// Holds a reference to a function that may be called.
#[derive(Debug)]
pub struct Function<'core> {
handle: NonNull<ffi::VSFuncRef>,
_owner: PhantomData<&'core ()>,
}
unsafe impl<'core> Send for Function<'core> {}
unsafe impl<'core> Sync for Function<'core> {}
impl<'core> Drop for Function<'core> {
#[inline]
fn drop(&mut self) {
unsafe {
API::get_cached().free_func(self.handle.as_ptr());
}
}
}
impl<'core> Clone for Function<'core> {
#[inline]
fn clone(&self) -> Self {
let handle = unsafe { API::get_cached().clone_func(self.handle.as_ptr()) };
Self {
handle: unsafe { NonNull::new_unchecked(handle) },
_owner: PhantomData,
}
}
}
impl<'core> Function<'core> {
/// Wraps `handle` in a `Function`.
///
/// # Safety
/// The caller must ensure `handle` and the lifetime are valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSFuncRef) -> Self {
Self {
handle: NonNull::new_unchecked(handle),
_owner: PhantomData,
}
}
/// Returns the underlying pointer.
#[inline]
pub(crate) fn ptr(&self) -> *mut ffi::VSFuncRef {
self.handle.as_ptr()
}
/// Creates a new function.
///
/// To indicate an error from the callback, set an error on the output map.
#[inline]
pub fn new<F>(api: API, core: CoreRef<'core>, callback: F) -> Self
where
F: Fn(API, CoreRef<'core>, &Map<'core>, &mut Map<'core>) + Send + Sync + 'core,
{
unsafe extern "system" fn c_callback<'core, F>(
in_: *const ffi::VSMap,
out: *mut ffi::VSMap,
user_data: *mut c_void,
core: *mut ffi::VSCore,
_vsapi: *const ffi::VSAPI,
) where
F: Fn(API, CoreRef<'core>, &Map<'core>, &mut Map<'core>) + Send + Sync + 'core,
{
let closure = move || {
let api = API::get_cached();
let core = CoreRef::from_ptr(core);
let in_ = MapRef::from_ptr(in_);
let mut out = MapRefMut::from_ptr(out);
let callback = Box::from_raw(user_data as *mut F);
callback(api, core, &in_, &mut out);
mem::forget(callback);
};
if panic::catch_unwind(closure).is_err() {
process::abort();
}
}
unsafe extern "system" fn c_free<F>(user_data: *mut c_void) {
drop(Box::from_raw(user_data as *mut F))
}
let data = Box::new(callback);
let handle = unsafe {
API::get_cached().create_func(
c_callback::<'core, F>,
Box::into_raw(data) as _,
Some(c_free::<F>),
core.ptr(),
)
};
Self {
handle: unsafe { NonNull::new_unchecked(handle) },
_owner: PhantomData,
}
}
/// Calls the function. If the call fails `out` will have an error set.
#[inline]
pub fn call(&self, in_: &Map<'core>, out: &mut Map<'core>) {
unsafe { API::get_cached().call_func(self.handle.as_ptr(), in_.deref(), out.deref_mut()) };
}
}

213
vapoursynth/src/lib.rs Normal file
View File

@ -0,0 +1,213 @@
//! A safe wrapper for [VapourSynth](https://github.com/vapoursynth/vapoursynth), written in Rust.
//!
//! The primary goal is safety (that is, safe Rust code should not trigger undefined behavior), and
//! secondary goals include performance and ease of use.
//!
//! ## Functionality
//!
//! 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.
//!
//! 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.
//!
//! For a VapourSynth plugin example see
//! [sample-plugin](https://github.com/YaLTeR/vapoursynth-rs/blob/master/sample-plugin) which
//! implements some simple filters.
//!
//! ## Short example
//!
//! ```no_run
//! # extern crate failure;
//! # extern crate vapoursynth;
//! # use failure::Error;
//! # #[cfg(all(feature = "vsscript-functions",
//! # feature = "gte-vsscript-api-31",
//! # any(feature = "vapoursynth-functions", feature = "gte-vsscript-api-32")))]
//! # fn foo() -> Result<(), Error> {
//! use vapoursynth::prelude::*;
//!
//! let env = Environment::from_file("test.vpy", EvalFlags::SetWorkingDir)?;
//! let node = env.get_output(0)?.0; // Without `.0` for VSScript API 3.0
//! let frame = node.get_frame(0)?;
//!
//! println!("Resolution: {}×{}", frame.width(0), frame.height(0));
//! # Ok(())
//! # }
//! # fn main() {
//! # }
//! ```
//!
//! ## Plugins
//!
//! To make a VapourSynth plugin, start by creating a new Rust library with
//! `crate-type = ["cdylib"]`. Then add filters by implementing the `plugins::Filter` trait. Bind
//! them to functions by implementing `plugins::FilterFunction`, which is much more easily done via
//! the `make_filter_function!` macro. Finally, put `export_vapoursynth_plugin!` at the top level
//! of `src/lib.rs` to export the functionality.
//!
//! **Important note:** due to what seems to be a
//! [bug](https://github.com/rust-lang/rust/issues/50176) in rustc, it's impossible to make plugins
//! on the `i686-pc-windows-gnu` target (all other variations of `x86_64` and `i686` do work).
//! Please use `i686-pc-windows-msvc` for an i686 Windows plugin.
//!
//! ## Short plugin example
//!
//! ```no_run
//! #[macro_use]
//! extern crate failure;
//! #[macro_use]
//! extern crate vapoursynth;
//!
//! use failure::Error;
//! use vapoursynth::prelude::*;
//! use vapoursynth::core::CoreRef;
//! use vapoursynth::plugins::{Filter, FilterArgument, FrameContext, Metadata};
//! use vapoursynth::video_info::VideoInfo;
//!
//! // A simple filter that passes the frames through unchanged.
//! struct Passthrough<'core> {
//! source: Node<'core>,
//! }
//!
//! impl<'core> Filter<'core> for Passthrough<'core> {
//! fn video_info(&self, _api: API, _core: CoreRef<'core>) -> Vec<VideoInfo<'core>> {
//! vec![self.source.info()]
//! }
//!
//! fn get_frame_initial(
//! &self,
//! _api: API,
//! _core: CoreRef<'core>,
//! context: FrameContext,
//! n: usize,
//! ) -> Result<Option<FrameRef<'core>>, Error> {
//! self.source.request_frame_filter(context, n);
//! Ok(None)
//! }
//!
//! fn get_frame(
//! &self,
//! _api: API,
//! _core: CoreRef<'core>,
//! context: FrameContext,
//! n: usize,
//! ) -> Result<FrameRef<'core>, Error> {
//! self.source
//! .get_frame_filter(context, n)
//! .ok_or(format_err!("Couldn't get the source frame"))
//! }
//! }
//!
//! make_filter_function! {
//! PassthroughFunction, "Passthrough"
//!
//! fn create_passthrough<'core>(
//! _api: API,
//! _core: CoreRef<'core>,
//! clip: Node<'core>,
//! ) -> Result<Option<Box<Filter<'core> + 'core>>, Error> {
//! Ok(Some(Box::new(Passthrough { source: clip })))
//! }
//! }
//!
//! export_vapoursynth_plugin! {
//! Metadata {
//! identifier: "com.example.passthrough",
//! namespace: "passthrough",
//! name: "Example Plugin",
//! read_only: true,
//! },
//! [PassthroughFunction::new()]
//! }
//! # fn main() {
//! # }
//! ```
//!
//! Check [sample-plugin](https://github.com/YaLTeR/vapoursynth-rs/blob/master/sample-plugin) for
//! an example plugin which exports some simple filters.
//!
//! ## Supported Versions
//!
//! 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:
//!
//! * `vapoursynth-api-31` for VapourSynth API 3.1
//! * `vapoursynth-api-32` for VapourSynth API 3.2
//! * `vapoursynth-api-33` for VapourSynth API 3.3
//! * `vapoursynth-api-34` for VapourSynth API 3.4
//! * `vapoursynth-api-35` for VapourSynth API 3.5
//! * `vsscript-api-31` for VSScript API 3.1
//! * `vsscript-api-32` for VSScript API 3.2
//!
//! To enable linking to VapourSynth or VSScript functions, enable the following Cargo features:
//!
//! * `vapoursynth-functions` for VapourSynth functions (`getVapourSynthAPI()`)
//! * `vsscript-functions` for VSScript functions (`vsscript_*()`)
//!
//! ## Building
//!
//! 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.
//!
//! 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
//! `<path to the VapourSynth installation>\sdk\lib64` or `<...>\lib32`, depending on the target
//! bitness.
#![doc(html_root_url = "https://docs.rs/vapoursynth/0.2.0")]
// Preventing all those warnings with #[cfg] directives would be really diffucult.
#![allow(unused, dead_code)]
#[macro_use]
extern crate bitflags;
extern crate failure;
#[macro_use]
extern crate failure_derive;
#[cfg(feature = "f16-pixel-type")]
extern crate half;
#[cfg(any(not(feature = "gte-vsscript-api-32"), test))]
#[macro_use]
extern crate lazy_static;
extern crate vapoursynth_sys;
#[cfg(feature = "vsscript-functions")]
pub mod vsscript;
pub mod api;
pub mod component;
pub mod core;
pub mod format;
pub mod frame;
pub mod function;
pub mod map;
pub mod node;
pub mod plugin;
pub mod plugins;
pub mod video_info;
pub mod prelude {
//! The VapourSynth prelude.
//!
//! Contains the types you most likely want to import anyway.
pub use super::api::{MessageType, API};
pub use super::component::Component;
pub use super::format::{ColorFamily, PresetFormat, SampleType};
pub use super::frame::{Frame, FrameRef, FrameRefMut};
pub use super::map::{Map, OwnedMap, ValueType};
pub use super::node::{GetFrameError, Node};
pub use super::plugin::Plugin;
pub use super::video_info::Property;
#[cfg(feature = "vsscript-functions")]
pub use super::vsscript::{self, Environment, EvalFlags};
}
mod tests;

View File

@ -0,0 +1,44 @@
use std::ffi::NulError;
use std::result;
/// The error type for `Map` operations.
#[derive(Fail, Debug, Eq, PartialEq)]
pub enum Error {
#[fail(display = "The requested key wasn't found in the map")]
KeyNotFound,
#[fail(display = "The requested index was out of bounds")]
IndexOutOfBounds,
#[fail(display = "The given/requested value type doesn't match the type of the property")]
WrongValueType,
#[fail(display = "The key is invalid")]
InvalidKey(#[cause] InvalidKeyError),
#[fail(display = "Couldn't convert to a CString")]
CStringConversion(#[cause] NulError),
}
/// A specialized `Result` type for `Map` operations.
pub type Result<T> = result::Result<T, Error>;
/// An error indicating the map key is invalid.
#[derive(Fail, Debug, Eq, PartialEq)]
#[cfg_attr(rustfmt, rustfmt_skip)]
pub enum InvalidKeyError {
#[fail(display = "The key is empty")]
EmptyKey,
#[fail(display = "The key contains an invalid character at index {}", _0)]
InvalidCharacter(usize),
}
impl From<InvalidKeyError> for Error {
#[inline]
fn from(x: InvalidKeyError) -> Self {
Error::InvalidKey(x)
}
}
impl From<NulError> for Error {
#[inline]
fn from(x: NulError) -> Self {
Error::CStringConversion(x)
}
}

View File

@ -0,0 +1,118 @@
use std::borrow::Cow;
use super::*;
/// An iterator over the keys of a map.
#[derive(Debug, Clone, Copy)]
pub struct Keys<'map, 'elem: 'map> {
map: &'map Map<'elem>,
count: usize,
index: usize,
}
impl<'map, 'elem> Keys<'map, 'elem> {
#[inline]
pub(crate) fn new(map: &'map Map<'elem>) -> Self {
Self {
map,
count: map.key_count(),
index: 0,
}
}
}
impl<'map, 'elem> Iterator for Keys<'map, 'elem> {
type Item = &'map str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let key = self.map.key(self.index);
self.index += 1;
Some(key)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.count - self.index;
(len, Some(len))
}
}
impl<'map, 'elem> ExactSizeIterator for Keys<'map, 'elem> {}
/// An iterator over the values associated with a certain key of a map.
#[derive(Debug, Clone)]
pub struct ValueIter<'map, 'elem: 'map, T> {
map: &'map Map<'elem>,
key: CString,
count: i32,
index: i32,
_variance: PhantomData<fn() -> T>,
}
macro_rules! impl_value_iter {
($value_type:path, $type:ty, $func:ident) => {
impl<'map, 'elem> ValueIter<'map, 'elem, $type> {
/// Creates a `ValueIter` from the given `map` and `key`.
///
/// # Safety
/// The caller must ensure `key` is valid.
#[inline]
pub(crate) unsafe fn new(map: &'map Map<'elem>, key: CString) -> Result<Self> {
// Check if the value type is correct.
match map.value_type_raw_unchecked(&key)? {
$value_type => {}
_ => return Err(Error::WrongValueType),
};
let count = map.value_count_raw_unchecked(&key)? as i32;
Ok(Self {
map,
key,
count,
index: 0,
_variance: PhantomData,
})
}
}
impl<'map, 'elem> Iterator for ValueIter<'map, 'elem, $type> {
type Item = $type;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let value = unsafe { self.map.$func(&self.key, self.index).unwrap() };
self.index += 1;
Some(value)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = (self.count - self.index) as usize;
(len, Some(len))
}
}
impl<'map, 'elem> ExactSizeIterator for ValueIter<'map, 'elem, $type> {}
};
}
impl_value_iter!(ValueType::Int, i64, get_int_raw_unchecked);
impl_value_iter!(ValueType::Float, f64, get_float_raw_unchecked);
impl_value_iter!(ValueType::Data, &'map [u8], get_data_raw_unchecked);
impl_value_iter!(ValueType::Node, Node<'elem>, get_node_raw_unchecked);
impl_value_iter!(ValueType::Frame, FrameRef<'elem>, get_frame_raw_unchecked);
impl_value_iter!(
ValueType::Function,
Function<'elem>,
get_function_raw_unchecked
);

1060
vapoursynth/src/map/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
use frame::FrameRef;
use function::Function;
use map::{Map, Result, ValueIter};
use node::Node;
/// An enumeration of all possible value types.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum ValueType {
Int,
Float,
Data,
Node,
Frame,
Function,
}
/// A trait for values which can be stored in a map.
pub trait Value<'map, 'elem: 'map>: Sized {
/// Retrieves the value from the map.
fn get_from_map(map: &'map Map<'elem>, key: &str) -> Result<Self>;
/// Retrieves an iterator over the values from the map.
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>>;
/// Sets the property value in the map.
fn store_in_map(map: &'map mut Map<'elem>, key: &str, x: &Self) -> Result<()>;
/// Appends the value to the map.
fn append_to_map(map: &'map mut Map<'elem>, key: &str, x: &Self) -> Result<()>;
}
impl<'map, 'elem: 'map> Value<'map, 'elem> for i64 {
#[inline]
fn get_from_map(map: &Map, key: &str) -> Result<Self> {
map.get_int(key)
}
#[inline]
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>> {
map.get_int_iter(key)
}
#[inline]
fn store_in_map(map: &mut Map, key: &str, x: &Self) -> Result<()> {
map.set_int(key, *x)
}
#[inline]
fn append_to_map(map: &mut Map, key: &str, x: &Self) -> Result<()> {
map.append_int(key, *x)
}
}
impl<'map, 'elem: 'map> Value<'map, 'elem> for f64 {
fn get_from_map(map: &Map, key: &str) -> Result<Self> {
map.get_float(key)
}
#[inline]
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>> {
map.get_float_iter(key)
}
#[inline]
fn store_in_map(map: &mut Map, key: &str, x: &Self) -> Result<()> {
map.set_float(key, *x)
}
#[inline]
fn append_to_map(map: &mut Map, key: &str, x: &Self) -> Result<()> {
map.append_float(key, *x)
}
}
impl<'map, 'elem: 'map> Value<'map, 'elem> for &'map [u8] {
#[inline]
fn get_from_map(map: &'map Map, key: &str) -> Result<Self> {
map.get_data(key)
}
#[inline]
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>> {
map.get_data_iter(key)
}
#[inline]
fn store_in_map(map: &'map mut Map, key: &str, x: &Self) -> Result<()> {
map.set_data(key, x)
}
#[inline]
fn append_to_map(map: &'map mut Map, key: &str, x: &Self) -> Result<()> {
map.append_data(key, x)
}
}
impl<'map, 'elem: 'map> Value<'map, 'elem> for Node<'elem> {
#[inline]
fn get_from_map(map: &Map<'elem>, key: &str) -> Result<Self> {
map.get_node(key)
}
#[inline]
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>> {
map.get_node_iter(key)
}
#[inline]
fn store_in_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> {
map.set_node(key, x)
}
#[inline]
fn append_to_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> {
map.append_node(key, x)
}
}
impl<'map, 'elem: 'map> Value<'map, 'elem> for FrameRef<'elem> {
#[inline]
fn get_from_map(map: &Map<'elem>, key: &str) -> Result<Self> {
map.get_frame(key)
}
#[inline]
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>> {
map.get_frame_iter(key)
}
#[inline]
fn store_in_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> {
map.set_frame(key, x)
}
#[inline]
fn append_to_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> {
map.append_frame(key, x)
}
}
impl<'map, 'elem: 'map> Value<'map, 'elem> for Function<'elem> {
#[inline]
fn get_from_map(map: &Map<'elem>, key: &str) -> Result<Self> {
map.get_function(key)
}
#[inline]
fn get_iter_from_map(map: &'map Map<'elem>, key: &str) -> Result<ValueIter<'map, 'elem, Self>> {
map.get_function_iter(key)
}
#[inline]
fn store_in_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> {
map.set_function(key, x)
}
#[inline]
fn append_to_map(map: &mut Map<'elem>, key: &str, x: &Self) -> Result<()> {
map.append_function(key, x)
}
}

View File

@ -0,0 +1,36 @@
use std::borrow::Cow;
use std::error::Error;
use std::ffi::CStr;
use std::fmt;
/// A container for a `get_frame` error.
#[derive(Debug)]
pub struct GetFrameError<'a>(Cow<'a, CStr>);
impl<'a> fmt::Display for GetFrameError<'a> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.to_string_lossy())
}
}
impl<'a> Error for GetFrameError<'a> {
#[inline]
fn description(&self) -> &str {
"VapourSynth error"
}
}
impl<'a> GetFrameError<'a> {
/// Creates a new `GetFrameError` with the given error message.
#[inline]
pub(crate) fn new(message: Cow<'a, CStr>) -> Self {
GetFrameError(message)
}
/// Consumes this error, returning its underlying error message.
#[inline]
pub fn into_inner(self) -> Cow<'a, CStr> {
self.0
}
}

280
vapoursynth/src/node/mod.rs Normal file
View File

@ -0,0 +1,280 @@
//! VapourSynth nodes.
use std::borrow::Cow;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_void};
use std::process;
use std::ptr::NonNull;
use std::{mem, panic};
use vapoursynth_sys as ffi;
use api::API;
use frame::FrameRef;
use plugins::FrameContext;
use video_info::VideoInfo;
mod errors;
pub use self::errors::GetFrameError;
bitflags! {
/// Node flags.
pub struct Flags: i32 {
/// This flag indicates that the frames returned by the filter should not be cached. "Fast"
/// filters should set this to reduce cache bloat.
const NO_CACHE = ffi::VSNodeFlags_nfNoCache.0;
/// This flag must not be used in third-party filters. It is used to mark instances of the
/// built-in Cache filter. Strange things may happen to your filter if you use this flag.
const IS_CACHE = ffi::VSNodeFlags_nfIsCache.0;
/// This flag should be used by filters which prefer linear access, like source filters,
/// where seeking around can cause significant slowdowns. This flag only has any effect if
/// the filter using it is immediately followed by an instance of the built-in Cache
/// filter.
#[cfg(feature = "gte-vapoursynth-api-33")]
const MAKE_LINEAR = ffi::VSNodeFlags_nfMakeLinear.0;
}
}
impl From<ffi::VSNodeFlags> for Flags {
#[inline]
fn from(flags: ffi::VSNodeFlags) -> Self {
Self::from_bits_truncate(flags.0)
}
}
/// A reference to a node in the constructed filter graph.
#[derive(Debug)]
pub struct Node<'core> {
handle: NonNull<ffi::VSNodeRef>,
_owner: PhantomData<&'core ()>,
}
unsafe impl<'core> Send for Node<'core> {}
unsafe impl<'core> Sync for Node<'core> {}
impl<'core> Drop for Node<'core> {
#[inline]
fn drop(&mut self) {
unsafe {
API::get_cached().free_node(self.handle.as_ptr());
}
}
}
impl<'core> Clone for Node<'core> {
#[inline]
fn clone(&self) -> Self {
let handle = unsafe { API::get_cached().clone_node(self.handle.as_ptr()) };
Self {
handle: unsafe { NonNull::new_unchecked(handle) },
_owner: PhantomData,
}
}
}
impl<'core> Node<'core> {
/// Wraps `handle` in a `Node`.
///
/// # Safety
/// The caller must ensure `handle` and the lifetime is valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSNodeRef) -> Self {
Self {
handle: NonNull::new_unchecked(handle),
_owner: PhantomData,
}
}
/// Returns the underlying pointer.
#[inline]
pub(crate) fn ptr(&self) -> *mut ffi::VSNodeRef {
self.handle.as_ptr()
}
/// Returns the video info associated with this `Node`.
// Since we don't store the pointer to the actual `ffi::VSVideoInfo` and the lifetime is that
// of the `ffi::VSFormat`, this returns `VideoInfo<'core>` rather than `VideoInfo<'a>`.
#[inline]
pub fn info(&self) -> VideoInfo<'core> {
unsafe {
let ptr = API::get_cached().get_video_info(self.handle.as_ptr());
VideoInfo::from_ptr(ptr)
}
}
/// Generates a frame directly.
///
/// The `'error` lifetime is unbounded because this function always returns owned data.
///
/// # Panics
/// Panics is `n` is greater than `i32::max_value()`.
pub fn get_frame<'error>(&self, n: usize) -> Result<FrameRef<'core>, GetFrameError<'error>> {
assert!(n <= i32::max_value() as usize);
let n = n as i32;
// Kinda arbitrary. Same value as used in vsvfw.
const ERROR_BUF_CAPACITY: usize = 32 * 1024;
let mut err_buf = Vec::with_capacity(ERROR_BUF_CAPACITY);
err_buf.resize(ERROR_BUF_CAPACITY, 0);
let mut err_buf = err_buf.into_boxed_slice();
let handle = unsafe { API::get_cached().get_frame(n, self.handle.as_ptr(), &mut *err_buf) };
if handle.is_null() {
// TODO: remove this extra allocation by reusing `Box<[c_char]>`.
let error = unsafe { CStr::from_ptr(err_buf.as_ptr()) }.to_owned();
Err(GetFrameError::new(Cow::Owned(error)))
} else {
Ok(unsafe { FrameRef::from_ptr(handle) })
}
}
/// Requests the generation of a frame. When the frame is ready, a user-provided function is
/// called.
///
/// If multiple frames were requested, they can be returned in any order.
///
/// The callback arguments are:
///
/// - the generated frame or an error message if the generation failed,
/// - the frame number (equal to `n`),
/// - the node that generated the frame (the same as `self`).
///
/// If the callback panics, the process is aborted.
///
/// # Panics
/// Panics is `n` is greater than `i32::max_value()`.
pub fn get_frame_async<F>(&self, n: usize, callback: F)
where
F: FnOnce(Result<FrameRef<'core>, GetFrameError>, usize, Node<'core>) + Send + 'core,
{
struct CallbackData<'core> {
callback: Box<CallbackFn<'core> + 'core>,
}
// A little bit of magic for Box<FnOnce>.
trait CallbackFn<'core> {
fn call(
self: Box<Self>,
frame: Result<FrameRef<'core>, GetFrameError>,
n: usize,
node: Node<'core>,
);
}
impl<'core, F> CallbackFn<'core> for F
where
F: FnOnce(Result<FrameRef<'core>, GetFrameError>, usize, Node<'core>),
{
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local))]
fn call(
self: Box<Self>,
frame: Result<FrameRef<'core>, GetFrameError>,
n: usize,
node: Node<'core>,
) {
(self)(frame, n, node)
}
}
unsafe extern "system" fn c_callback(
user_data: *mut c_void,
frame: *const ffi::VSFrameRef,
n: i32,
node: *mut ffi::VSNodeRef,
error_msg: *const c_char,
) {
// The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
// retrieving it.
let user_data = Box::from_raw(user_data as *mut CallbackData<'static>);
let closure = panic::AssertUnwindSafe(move || {
let frame = if frame.is_null() {
debug_assert!(!error_msg.is_null());
let error_msg = Cow::Borrowed(CStr::from_ptr(error_msg));
Err(GetFrameError::new(error_msg))
} else {
debug_assert!(error_msg.is_null());
Ok(FrameRef::from_ptr(frame))
};
let node = Node::from_ptr(node);
debug_assert!(n >= 0);
let n = n as usize;
user_data.callback.call(frame, n, node);
});
if panic::catch_unwind(closure).is_err() {
process::abort();
}
}
assert!(n <= i32::max_value() as usize);
let n = n as i32;
let user_data = Box::new(CallbackData {
callback: Box::new(callback),
});
let new_node = self.clone();
unsafe {
API::get_cached().get_frame_async(
n,
new_node.handle.as_ptr(),
Some(c_callback),
Box::into_raw(user_data) as *mut c_void,
);
}
// It'll be dropped by the callback.
mem::forget(new_node);
}
/// Requests a frame from a node and returns immediately.
///
/// This is only used in filters' "get frame" functions.
///
/// A filter usually calls this function from `get_frame_initial()`. The requested frame can
/// then be retrieved using `get_frame_filter()` from within filter's `get_frame()` function.
///
/// It is safe to request a frame more than once. An unimportant consequence of requesting a
/// frame more than once is that the filter's `get_frame()` function may be called more than
/// once for the same frame.
///
/// It is best to request frames in ascending order, i.e. `n`, `n+1`, `n+2`, etc.
///
/// # Panics
/// Panics is `n` is greater than `i32::max_value()`.
pub fn request_frame_filter(&self, context: FrameContext, n: usize) {
assert!(n <= i32::max_value() as usize);
let n = n as i32;
unsafe {
API::get_cached().request_frame_filter(n, self.ptr(), context.ptr());
}
}
/// Retrieves a frame that was previously requested with `request_frame_filter()`.
///
/// A filter usually calls this function from `get_frame()`. It is safe to retrieve a frame
/// more than once.
///
/// # Panics
/// Panics is `n` is greater than `i32::max_value()`.
pub fn get_frame_filter(&self, context: FrameContext, n: usize) -> Option<FrameRef<'core>> {
assert!(n <= i32::max_value() as usize);
let n = n as i32;
let ptr = unsafe { API::get_cached().get_frame_filter(n, self.ptr(), context.ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { FrameRef::from_ptr(ptr) })
}
}
}

112
vapoursynth/src/plugin.rs Normal file
View File

@ -0,0 +1,112 @@
//! VapourSynth plugins.
use std::ffi::{CStr, CString, NulError};
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
use vapoursynth_sys as ffi;
use api::API;
use map::{Map, OwnedMap};
use plugins::{self, FilterFunction};
/// A VapourSynth plugin.
#[derive(Debug, Clone, Copy)]
pub struct Plugin<'core> {
handle: NonNull<ffi::VSPlugin>,
_owner: PhantomData<&'core ()>,
}
unsafe impl<'core> Send for Plugin<'core> {}
unsafe impl<'core> Sync for Plugin<'core> {}
impl<'core> Plugin<'core> {
/// Wraps `handle` in a `Plugin`.
///
/// # Safety
/// The caller must ensure `handle` is valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSPlugin) -> Self {
Self {
handle: NonNull::new_unchecked(handle),
_owner: PhantomData,
}
}
/// Returns a map containing a list of the filters exported by a plugin.
///
/// Keys: the filter names;
///
/// Values: the filter name followed by its argument string, separated by a semicolon.
// TODO: parse the values on the crate side and return a nice struct.
#[inline]
pub fn functions(&self) -> OwnedMap<'core> {
unsafe { OwnedMap::from_ptr(API::get_cached().get_functions(self.handle.as_ptr())) }
}
/// Returns the absolute path to the plugin, including the plugin's file name. This is the real
/// location of the plugin, i.e. there are no symbolic links in the path.
///
/// Path elements are always delimited with forward slashes.
#[cfg(feature = "gte-vapoursynth-api-31")]
#[inline]
pub fn path(&self) -> Option<&'core CStr> {
let ptr = unsafe { API::get_cached().get_plugin_path(self.handle.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr) })
}
}
/// Invokes a filter.
///
/// `invoke()` makes sure the filter has no compat input nodes, checks that the args passed to
/// the filter are consistent with the argument list registered by the plugin that contains the
/// filter, creates the filter, and checks that the filter doesn't return any compat nodes. If
/// everything goes smoothly, the filter will be ready to generate frames after `invoke()`
/// returns.
///
/// Returns a map containing the filter's return value(s). Use `Map::error()` to check if the
/// filter was invoked successfully.
///
/// Most filters will either add an error to the map, or one or more clips with the key `clip`.
/// The exception to this are functions, for example `LoadPlugin`, which doesn't return any
/// clips for obvious reasons.
#[inline]
pub fn invoke(&self, name: &str, args: &Map<'core>) -> Result<OwnedMap<'core>, NulError> {
let name = CString::new(name)?;
Ok(unsafe {
OwnedMap::from_ptr(API::get_cached().invoke(
self.handle.as_ptr(),
name.as_ptr(),
args.deref(),
))
})
}
/// Registers a filter function to be exported by a non-readonly plugin.
#[inline]
pub fn register_function<F: FilterFunction>(&self, filter_function: F) -> Result<(), NulError> {
// TODO: this is almost the same code as plugins::ffi::call_register_function().
let name_cstring = CString::new(filter_function.name())?;
let args_cstring = CString::new(filter_function.args())?;
let data = Box::new(plugins::ffi::FilterFunctionData::<F> {
filter_function,
name: name_cstring,
});
unsafe {
API::get_cached().register_function(
data.name.as_ptr(),
args_cstring.as_ptr(),
plugins::ffi::create::<F>,
Box::into_raw(data) as _,
self.handle.as_ptr(),
);
}
Ok(())
}
}

View File

@ -0,0 +1,346 @@
//! Internal stuff for plugin FFI handling.
use failure::Error;
use std::ffi::CString;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_void;
use std::ptr::{self, NonNull};
use std::{mem, panic, process};
use vapoursynth_sys as ffi;
use api::API;
use core::CoreRef;
use frame::FrameRef;
use map::{Map, MapRef, MapRefMut};
use plugins::{Filter, FilterFunction, FrameContext, Metadata};
use video_info::VideoInfo;
/// Container for the internal filter function data.
pub(crate) struct FilterFunctionData<F: FilterFunction> {
pub filter_function: F,
// Store the name since it's supposed to be the same between two invocations (register and
// create_filter).
pub name: CString,
}
/// Pushes the error backtrace into the given string.
fn push_backtrace(buf: &mut String, err: &Error) {
for cause in err.causes().skip(1) {
buf.push_str(&format!("Caused by: {}", cause));
}
buf.push_str(&format!("{}", err.backtrace()));
}
/// Sets the video info of the output node of this filter.
unsafe extern "system" fn init(
_in_: *mut ffi::VSMap,
out: *mut ffi::VSMap,
instance_data: *mut *mut c_void,
node: *mut ffi::VSNode,
core: *mut ffi::VSCore,
_vsapi: *const ffi::VSAPI,
) {
let closure = move || {
let core = CoreRef::from_ptr(core);
// The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
// retrieving it.
let filter = Box::from_raw(*(instance_data as *mut *mut Box<Filter<'static> + 'static>));
let vi = filter
.video_info(API::get_cached(), core)
.into_iter()
.map(VideoInfo::ffi_type)
.collect::<Vec<_>>();
API::get_cached().set_video_info(&vi, node);
mem::forget(filter);
};
if panic::catch_unwind(closure).is_err() {
let closure = move || {
// We have to leak filter here because we can't guarantee that it's in a consistent
// state after a panic.
//
// Just set the error message.
let mut out = MapRefMut::from_ptr(out);
out.set_error("Panic during Filter::video_info()");
};
if panic::catch_unwind(closure).is_err() {
process::abort();
}
}
}
/// Drops the filter.
unsafe extern "system" fn free(
instance_data: *mut c_void,
core: *mut ffi::VSCore,
_vsapi: *const ffi::VSAPI,
) {
let closure = move || {
// The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
// retrieving it.
let filter = Box::from_raw(instance_data as *mut Box<Filter<'static> + 'static>);
drop(filter);
};
if panic::catch_unwind(closure).is_err() {
process::abort();
}
}
/// Calls `Filter::get_frame_initial()` and `Filter::get_frame()`.
unsafe extern "system" fn get_frame(
n: i32,
activation_reason: i32,
instance_data: *mut *mut c_void,
_frame_data: *mut *mut c_void,
frame_ctx: *mut ffi::VSFrameContext,
core: *mut ffi::VSCore,
_vsapi: *const ffi::VSAPI,
) -> *const ffi::VSFrameRef {
let closure = move || {
let api = API::get_cached();
let core = CoreRef::from_ptr(core);
let context = FrameContext::from_ptr(frame_ctx);
// The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
// retrieving it.
let filter = Box::from_raw(*(instance_data as *mut *mut Box<Filter<'static> + 'static>));
debug_assert!(n >= 0);
let n = n as usize;
let rv = match activation_reason {
x if x == ffi::VSActivationReason::arInitial as _ => {
match filter.get_frame_initial(api, core, context, n) {
Ok(Some(frame)) => {
let ptr = frame.deref().deref() as *const _;
// The ownership is transferred to the caller.
mem::forget(frame);
ptr
}
Ok(None) => ptr::null(),
Err(err) => {
let mut buf = String::new();
buf += &format!("Error in Filter::get_frame_initial(): {}", err.cause());
push_backtrace(&mut buf, &err);
let buf = CString::new(buf.replace('\0', "\\0")).unwrap();
api.set_filter_error(buf.as_ptr(), frame_ctx);
ptr::null()
}
}
}
x if x == ffi::VSActivationReason::arAllFramesReady as _ => {
match filter.get_frame(api, core, context, n) {
Ok(frame) => {
let ptr = frame.deref().deref() as *const _;
// The ownership is transferred to the caller.
mem::forget(frame);
ptr
}
Err(err) => {
let mut buf = String::new();
buf += &format!("{}", err.cause());
push_backtrace(&mut buf, &err);
let buf = CString::new(buf.replace('\0', "\\0")).unwrap();
api.set_filter_error(buf.as_ptr(), frame_ctx);
ptr::null()
}
}
}
_ => ptr::null(),
};
mem::forget(filter);
rv
};
match panic::catch_unwind(closure) {
Ok(frame) => frame,
Err(_) => process::abort(),
}
}
/// Creates a new instance of the filter.
pub(crate) unsafe extern "system" fn create<F: FilterFunction>(
in_: *const ffi::VSMap,
out: *mut ffi::VSMap,
user_data: *mut c_void,
core: *mut ffi::VSCore,
api: *const ffi::VSAPI,
) {
let closure = move || {
API::set(api);
let args = MapRef::from_ptr(in_);
let mut out = MapRefMut::from_ptr(out);
let core = CoreRef::from_ptr(core);
let data = Box::from_raw(user_data as *mut FilterFunctionData<F>);
let filter = match data.filter_function.create(API::get_cached(), core, &args) {
Ok(Some(filter)) => Some(Box::new(filter)),
Ok(None) => None,
Err(err) => {
let mut buf = String::new();
buf += &format!(
"Error in Filter::create() of {}: {}",
data.name.to_str().unwrap(),
err.cause()
);
push_backtrace(&mut buf, &err);
out.set_error(&buf.replace('\0', "\\0")).unwrap();
None
}
};
if let Some(filter) = filter {
API::get_cached().create_filter(
in_,
out.deref_mut().deref_mut(),
data.name.as_ptr(),
init,
get_frame,
Some(free),
ffi::VSFilterMode::fmParallel,
ffi::VSNodeFlags(0),
Box::into_raw(filter) as *mut _,
core.ptr(),
);
}
mem::forget(data);
};
if panic::catch_unwind(closure).is_err() {
// The `FilterFunction` might have been left in an inconsistent state, so we have to abort.
process::abort();
}
}
/// Registers the plugin.
///
/// This function is for internal use only.
///
/// # Safety
/// The caller must ensure the pointers are valid.
#[inline]
pub unsafe fn call_config_func(
config_func: *const c_void,
plugin: *mut c_void,
metadata: Metadata,
) {
let config_func = *(&config_func as *const _ as *const ffi::VSConfigPlugin);
let identifier_cstring = CString::new(metadata.identifier)
.expect("Couldn't convert the plugin identifier to a CString");
let namespace_cstring = CString::new(metadata.namespace)
.expect("Couldn't convert the plugin namespace to a CString");
let name_cstring =
CString::new(metadata.name).expect("Couldn't convert the plugin name to a CString");
config_func(
identifier_cstring.as_ptr(),
namespace_cstring.as_ptr(),
name_cstring.as_ptr(),
ffi::VAPOURSYNTH_API_VERSION,
if metadata.read_only { 1 } else { 0 },
plugin as *mut ffi::VSPlugin,
);
}
/// Registers the filter `F`.
///
/// This function is for internal use only.
///
/// # Safety
/// The caller must ensure the pointers are valid.
#[inline]
pub unsafe fn call_register_func<F: FilterFunction>(
register_func: *const c_void,
plugin: *mut c_void,
filter_function: F,
) {
let register_func = *(&register_func as *const _ as *const ffi::VSRegisterFunction);
let name_cstring = CString::new(filter_function.name())
.expect("Couldn't convert the filter name to a CString");
let args_cstring = CString::new(filter_function.args())
.expect("Couldn't convert the filter args to a CString");
let data = Box::new(FilterFunctionData {
filter_function,
name: name_cstring,
});
register_func(
data.name.as_ptr(),
args_cstring.as_ptr(),
create::<F>,
Box::into_raw(data) as _,
plugin as *mut ffi::VSPlugin,
);
}
/// Exports a VapourSynth plugin from this library.
///
/// This macro should be used only once at the top level of the library. The library should have a
/// `cdylib` crate type.
///
/// The first parameter is a `Metadata` expression containing your plugin's metadata.
///
/// Following it is a list of values implementing `FilterFunction`, those are the filter functions
/// the plugin will export.
///
/// # Example
/// ```ignore
/// export_vapoursynth_plugin! {
/// Metadata {
/// identifier: "com.example.invert",
/// namespace: "invert",
/// name: "Invert Example Plugin",
/// read_only: true,
/// },
/// [SampleFilterFunction::new(), OtherFunction::new()]
/// }
/// ```
#[macro_export]
macro_rules! export_vapoursynth_plugin {
($metadata:expr, [$($filter:expr),*$(,)*]) => (
use ::std::os::raw::c_void;
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "system" fn VapourSynthPluginInit(
config_func: *const c_void,
register_func: *const c_void,
plugin: *mut c_void,
) {
use ::std::{panic, process};
use $crate::plugins::ffi::{call_config_func, call_register_func};
let closure = move || {
call_config_func(config_func, plugin, $metadata);
$(call_register_func(register_func, plugin, $filter);)*
};
if panic::catch_unwind(closure).is_err() {
process::abort();
}
}
)
}

View File

@ -0,0 +1,40 @@
use std::marker::PhantomData;
use std::ptr::NonNull;
use vapoursynth_sys as ffi;
use api::API;
/// A frame context used in filters.
#[derive(Debug, Clone, Copy)]
pub struct FrameContext<'a> {
handle: NonNull<ffi::VSFrameContext>,
_owner: PhantomData<&'a ()>,
}
impl<'a> FrameContext<'a> {
/// Wraps `handle` in a `FrameContext`.
///
/// # Safety
/// The caller must ensure `handle` is valid and API is cached.
#[inline]
pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSFrameContext) -> Self {
Self {
handle: NonNull::new_unchecked(handle),
_owner: PhantomData,
}
}
/// Returns the underlying pointer.
#[inline]
pub(crate) fn ptr(self) -> *mut ffi::VSFrameContext {
self.handle.as_ptr()
}
/// Returns the index of the node from which the frame is being requested.
#[inline]
pub fn output_index(self) -> usize {
let index = unsafe { API::get_cached().get_output_index(self.handle.as_ptr()) };
debug_assert!(index >= 0);
index as _
}
}

View File

@ -0,0 +1,464 @@
//! Things related to making VapourSynth plugins.
use failure::Error;
use api::API;
use core::CoreRef;
use frame::FrameRef;
use function::Function;
use map::{self, Map, Value, ValueIter};
use node::Node;
use video_info::VideoInfo;
mod frame_context;
pub use self::frame_context::FrameContext;
pub mod ffi;
/// Plugin metadata.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Metadata {
/// A "reverse" URL, unique among all plugins.
///
/// For example, `com.example.invert`.
pub identifier: &'static str,
/// Namespace where the plugin's filters will go, unique among all plugins.
///
/// Only lowercase letters and the underscore should be used, and it shouldn't be too long.
/// Additionally, words that are special to Python, e.g. `del`, should be avoided.
///
/// For example, `invert`.
pub namespace: &'static str,
/// Plugin name in readable form.
///
/// For example, `Invert Example Plugin`.
pub name: &'static str,
/// Whether new filters can be registered at runtime.
///
/// This should generally be set to `false`. It's used for the built-in AviSynth compat plugin.
pub read_only: bool,
}
/// A filter function interface.
///
/// See the `make_filter_function!` macro that generates types implementing this automatically.
pub trait FilterFunction: Send + Sync {
/// Returns the name of the function.
///
/// The characters allowed are letters, numbers, and the underscore. The first character must
/// be a letter. In other words: `^[a-zA-Z][a-zA-Z0-9_]*$`.
///
/// For example, `Invert`.
fn name(&self) -> &str;
/// Returns the argument string.
///
/// Arguments are separated by a semicolon. Each argument is made of several fields separated
/// by a colon. Don’t insert additional whitespace characters, or VapourSynth will die.
///
/// Fields:
/// - The argument name. The same characters are allowed as for the filter's name. Argument
/// names should be all lowercase and use only letters and the underscore.
///
/// - The type. One of `int`, `float`, `data`, `clip`, `frame`, `func`. They correspond to the
/// `Map::get_*()` functions (`clip` is `get_node()`). It's possible to declare an array by
/// appending `[]` to the type.
///
/// - `opt` if the parameter is optional.
///
/// - `empty` if the array is allowed to be empty.
///
/// The following example declares the arguments "blah", "moo", and "asdf":
/// `blah:clip;moo:int[]:opt;asdf:float:opt;`
fn args(&self) -> &str;
/// The callback for this filter function.
///
/// In most cases this is where you should create a new instance of the filter and return it.
/// However, a filter function like AviSynth compat's `LoadPlugin()` which isn't actually a
/// filter, can return `None`.
///
/// `args` contains the filter arguments, as specified by the argument string from
/// `FilterFunction::args()`. Their presence and types are validated by VapourSynth so it's
/// safe to `unwrap()`.
///
/// In this function you should take all input nodes for your filter and store them somewhere
/// so that you can request their frames in `get_frame_initial()`.
// TODO: with generic associated types it'll be possible to make Filter<'core> an associated
// type of this trait and get rid of this Box.
fn create<'core>(
&self,
api: API,
core: CoreRef<'core>,
args: &Map<'core>,
) -> Result<Option<Box<Filter<'core> + 'core>>, Error>;
}
/// A filter interface.
// TODO: perhaps it's possible to figure something out about Send + Sync with specialization? Since
// there are Node flags which say that the filter will be called strictly by one thread, in which
// case Sync shouldn't be required.
pub trait Filter<'core>: Send + Sync {
/// Returns the parameters of this filter's output node.
///
/// The returned vector should contain one entry for each node output index.
fn video_info(&self, api: API, core: CoreRef<'core>) -> Vec<VideoInfo<'core>>;
/// Requests the necessary frames from downstream nodes.
///
/// This is always the first function to get called for a given frame `n`.
///
/// In this function you should call `request_frame_filter()` on any input nodes that you need
/// and return `None`. If you do not need any input frames, you should generate the output
/// frame and return it here.
///
/// Do not call `Node::get_frame()` from within this function.
fn get_frame_initial(
&self,
api: API,
core: CoreRef<'core>,
context: FrameContext,
n: usize,
) -> Result<Option<FrameRef<'core>>, Error>;
/// Returns the requested frame.
///
/// This is always the second function to get called for a given frame `n`. If the frame was
/// retrned from `get_frame_initial()`, this function is not called.
///
/// In this function you should call `get_frame_filter()` on the input nodes to retrieve the
/// frames you requested in `get_frame_initial()`.
///
/// Do not call `Node::get_frame()` from within this function.
fn get_frame(
&self,
api: API,
core: CoreRef<'core>,
context: FrameContext,
n: usize,
) -> Result<FrameRef<'core>, Error>;
}
/// An internal trait representing a filter argument type.
pub trait FilterArgument<'map, 'elem: 'map>: Value<'map, 'elem> + private::Sealed {
/// Returns the VapourSynth type name for this argument type.
fn type_name() -> &'static str;
}
/// An internal trait representing a filter parameter type (argument type + whether it's an array
/// or optional).
pub trait FilterParameter<'map, 'elem: 'map>: private::Sealed {
/// The underlying argument type for this parameter type.
type Argument: FilterArgument<'map, 'elem>;
/// Returns whether this parameter is an array.
fn is_array() -> bool;
/// Returns whether this parameter is optional.
fn is_optional() -> bool;
/// Retrieves this parameter from the given map.
fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self;
}
impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for i64 {
#[inline]
fn type_name() -> &'static str {
"int"
}
}
impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for f64 {
#[inline]
fn type_name() -> &'static str {
"float"
}
}
impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for &'map [u8] {
#[inline]
fn type_name() -> &'static str {
"data"
}
}
impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for Node<'elem> {
#[inline]
fn type_name() -> &'static str {
"clip"
}
}
impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for FrameRef<'elem> {
#[inline]
fn type_name() -> &'static str {
"frame"
}
}
impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for Function<'elem> {
#[inline]
fn type_name() -> &'static str {
"func"
}
}
impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for T
where
T: FilterArgument<'map, 'elem>,
{
type Argument = Self;
#[inline]
fn is_array() -> bool {
false
}
#[inline]
fn is_optional() -> bool {
false
}
#[inline]
fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
Self::get_from_map(map, key).unwrap()
}
}
impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for Option<T>
where
T: FilterArgument<'map, 'elem>,
{
type Argument = T;
#[inline]
fn is_array() -> bool {
false
}
#[inline]
fn is_optional() -> bool {
true
}
#[inline]
fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
match <Self::Argument as Value>::get_from_map(map, key) {
Ok(x) => Some(x),
Err(map::Error::KeyNotFound) => None,
_ => unreachable!(),
}
}
}
impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for ValueIter<'map, 'elem, T>
where
T: FilterArgument<'map, 'elem>,
{
type Argument = T;
#[inline]
fn is_array() -> bool {
true
}
#[inline]
fn is_optional() -> bool {
false
}
#[inline]
fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
<Self::Argument>::get_iter_from_map(map, key).unwrap()
}
}
impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for Option<ValueIter<'map, 'elem, T>>
where
T: FilterArgument<'map, 'elem>,
{
type Argument = T;
#[inline]
fn is_array() -> bool {
true
}
#[inline]
fn is_optional() -> bool {
true
}
#[inline]
fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
match <Self::Argument as Value>::get_iter_from_map(map, key) {
Ok(x) => Some(x),
Err(map::Error::KeyNotFound) => None,
_ => unreachable!(),
}
}
}
mod private {
use super::{FilterArgument, FrameRef, Function, Node, ValueIter};
pub trait Sealed {}
impl Sealed for i64 {}
impl Sealed for f64 {}
impl<'map> Sealed for &'map [u8] {}
impl<'elem> Sealed for Node<'elem> {}
impl<'elem> Sealed for FrameRef<'elem> {}
impl<'elem> Sealed for Function<'elem> {}
impl<'map, 'elem: 'map, T> Sealed for Option<T> where T: FilterArgument<'map, 'elem> {}
impl<'map, 'elem: 'map, T> Sealed for ValueIter<'map, 'elem, T> where T: FilterArgument<'map, 'elem> {}
impl<'map, 'elem: 'map, T> Sealed for Option<ValueIter<'map, 'elem, T>> where
T: FilterArgument<'map, 'elem>
{}
}
/// Make a filter function easily and avoid boilerplate.
///
/// This macro accepts the name of the filter function type, the name of the filter and the create
/// function.
///
/// The macro generates a type implementing `FilterFunction` with the correct `args()` string
/// derived from the function parameters of the specified create function. The generated
/// `FilterFunction::create()` extracts all parameters from the argument map received from
/// VapourSynth and passes them into the specified create function.
///
/// The create function should look like:
///
/// ```ignore
/// fn create<'core>(
/// api: API,
/// core: CoreRef<'core>,
/// /* filter arguments */
/// ) -> Result<Option<Box<Filter<'core> + 'core>>, Error> {
/// /* ... */
/// }
/// ```
///
/// All VapourSynth-supported types can be used, as well as `Option<T>` for optional parameters and
/// `ValueIter<T>` for array parameters. Array parameters can be empty.
///
/// Caveat: the macro doesn't currently allow specifying mutable parameters, so to do that they
/// have to be reassigned to a mutable variable in the function body. This is mainly a problem for
/// array parameters. See how the example below handles it.
///
/// Another caveat: underscore lifetimes are required for receiving `ValueIter<T>`.
///
/// # Example
/// ```ignore
/// make_filter_function! {
/// MyFilterFunction, "MyFilter"
///
/// fn create_my_filter<'core>(
/// _api: API,
/// _core: CoreRef<'core>,
/// int_parameter: i64,
/// some_data: &[u8],
/// optional_parameter: Option<f64>,
/// array_parameter: ValueIter<'_, 'core, Node<'core>>,
/// optional_array_parameter: Option<ValueIter<'_, 'core, FrameRef<'core>>>,
/// ) -> Result<Option<Box<Filter<'core> + 'core>>, Error> {
/// let mut array_parameter = array_parameter;
/// Ok(Some(Box::new(MyFilter::new(/* ... */))));
/// }
/// }
/// ```
#[macro_export]
macro_rules! make_filter_function {
(
$struct_name:ident, $function_name:tt
$(#[$attr:meta])*
fn $create_fn_name:ident<$lifetime:tt>(
$api_arg_name:ident : $api_arg_type:ty,
$core_arg_name:ident : $core_arg_type:ty,
$($arg_name:ident : $arg_type:ty),* $(,)*
) -> $return_type:ty {
$($body:tt)*
}
) => (
struct $struct_name {
args: String,
}
impl $struct_name {
fn new<'core>() -> Self {
let mut args = String::new();
$(
// Don't use format!() for better constant propagation.
args += stringify!($arg_name); // TODO: allow using a different name.
args += ":";
args
+= <<$arg_type as $crate::plugins::FilterParameter>::Argument>::type_name();
if <$arg_type as $crate::plugins::FilterParameter>::is_array() {
args += "[]";
}
if <$arg_type as $crate::plugins::FilterParameter>::is_optional() {
args += ":opt";
}
// TODO: allow specifying this.
if <$arg_type as $crate::plugins::FilterParameter>::is_array() {
args += ":empty";
}
args += ";";
)*
Self { args }
}
}
impl $crate::plugins::FilterFunction for $struct_name {
#[inline]
fn name(&self) -> &str {
$function_name
}
#[inline]
fn args(&self) -> &str {
&self.args
}
#[inline]
fn create<'core>(
&self,
api: API,
core: CoreRef<'core>,
args: &Map<'core>,
) -> Result<Option<Box<$crate::plugins::Filter<'core> + 'core>>, Error> {
$create_fn_name(
api,
core,
$(
<$arg_type as $crate::plugins::FilterParameter>::get_from_map(
args,
stringify!($arg_name),
)
),*
)
}
}
$(#[$attr])*
fn $create_fn_name<$lifetime>(
$api_arg_name : $api_arg_type,
$core_arg_name : $core_arg_type,
$($arg_name : $arg_type),*
) -> $return_type {
$($body)*
}
)
}

905
vapoursynth/src/tests.rs Normal file
View File

@ -0,0 +1,905 @@
#![cfg(test)]
use super::*;
// We need the VSScript functions, and either VSScript API 3.2 or the VapourSynth functions.
#[cfg(all(
feature = "vsscript-functions",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
))]
mod need_api_and_vsscript {
use std::ffi::CStr;
use std::fmt::Debug;
use std::mem;
use std::slice;
use std::sync::mpsc::channel;
use super::*;
use function::Function;
use prelude::*;
use video_info::{Framerate, Resolution};
fn props_test(frame: &Frame, fps_num: i64) {
let props = frame.props();
assert_eq!(props.key_count(), 2);
assert_eq!(props.key(0), "_DurationDen");
assert_eq!(props.key(1), "_DurationNum");
assert_eq!(props.value_count(props.key(0)), Ok(1));
assert_eq!(props.get_int(props.key(0)), Ok(fps_num));
assert_eq!(props.value_count(props.key(1)), Ok(1));
assert_eq!(props.get_int(props.key(1)), Ok(1));
}
fn env_video_var_test(env: &vsscript::Environment) {
let mut map = OwnedMap::new(API::get().unwrap());
assert!(env.get_variable("video", &mut map).is_ok());
assert_eq!(map.key_count(), 1);
assert_eq!(map.key(0), "video");
let node = map.get_node("video");
assert!(node.is_ok());
}
fn green_frame_test(frame: &Frame) {
let format = frame.format();
assert_eq!(format.name(), "RGB24");
assert_eq!(format.plane_count(), 3);
for plane in 0..format.plane_count() {
let resolution = frame.resolution(plane);
assert_eq!(
resolution,
Resolution {
width: 1920,
height: 1080,
}
);
let color = if plane == 1 { [255; 1920] } else { [0; 1920] };
for row in 0..resolution.height {
let data_row = frame.data_row(plane, row);
assert_eq!(&data_row[..], &color[..]);
let data_row = frame.plane_row::<u8>(plane, row);
assert_eq!(&data_row[..], &color[..]);
}
}
}
fn green_test(env: &vsscript::Environment) {
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = {
let output = env.get_output(0);
assert!(output.is_ok());
output.unwrap()
};
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (env.get_output(0).unwrap(), None::<Node>);
assert!(alpha_node.is_none());
let info = node.info();
if let Property::Constant(format) = info.format {
assert_eq!(format.name(), "RGB24");
} else {
assert!(false);
}
assert_eq!(
info.framerate,
Property::Constant(Framerate {
numerator: 60,
denominator: 1,
})
);
assert_eq!(
info.resolution,
Property::Constant(Resolution {
width: 1920,
height: 1080,
})
);
#[cfg(feature = "gte-vapoursynth-api-32")]
assert_eq!(info.num_frames, 100);
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
assert_eq!(info.num_frames, Property::Constant(100));
let frame = node.get_frame(0).unwrap();
green_frame_test(&frame);
props_test(&frame, 60);
env_video_var_test(&env);
}
#[test]
fn green() {
let env =
vsscript::Environment::from_file("test-vpy/green.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
green_test(&env);
}
#[test]
fn green_from_string() {
let env =
vsscript::Environment::from_script(include_str!("../test-vpy/green.vpy")).unwrap();
green_test(&env);
}
#[test]
fn variable() {
let env =
vsscript::Environment::from_file("test-vpy/variable.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = {
let output = env.get_output(0);
assert!(output.is_ok());
output.unwrap()
};
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (env.get_output(0).unwrap(), None::<Node>);
assert!(alpha_node.is_none());
let info = node.info();
assert_eq!(info.format, Property::Variable);
assert_eq!(info.framerate, Property::Variable);
assert_eq!(info.resolution, Property::Variable);
#[cfg(feature = "gte-vapoursynth-api-32")]
assert_eq!(info.num_frames, 200);
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
assert_eq!(info.num_frames, Property::Constant(200));
// Test the first frame.
let frame = node.get_frame(0).unwrap();
let format = frame.format();
assert_eq!(format.name(), "RGB24");
assert_eq!(format.plane_count(), 3);
for plane in 0..format.plane_count() {
let resolution = frame.resolution(plane);
assert_eq!(
resolution,
Resolution {
width: 1920,
height: 1080,
}
);
let color = if plane == 1 { [255; 1920] } else { [0; 1920] };
for row in 0..resolution.height {
let data_row = frame.data_row(plane, row);
assert_eq!(&data_row[..], &color[..]);
let data_row = frame.plane_row::<u8>(plane, row);
assert_eq!(&data_row[..], &color[..]);
}
}
props_test(&frame, 60);
// Test the first frame of the next format.
let frame = node.get_frame(100).unwrap();
let format = frame.format();
assert_eq!(format.name(), "Gray8");
assert_eq!(format.plane_count(), 1);
let plane = 0;
let resolution = frame.resolution(plane);
assert_eq!(
resolution,
Resolution {
width: 1280,
height: 720,
}
);
let color = [127; 1280];
for row in 0..resolution.height {
let data_row = frame.data_row(plane, row);
assert_eq!(&data_row[..], &color[..]);
let data_row = frame.plane_row::<u8>(plane, row);
assert_eq!(&data_row[..], &color[..]);
}
props_test(&frame, 30);
env_video_var_test(&env);
}
#[test]
#[cfg(feature = "gte-vsscript-api-31")]
fn alpha() {
let env =
vsscript::Environment::from_file("test-vpy/alpha.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
let (_, alpha_node) = {
let output = env.get_output(0);
assert!(output.is_ok());
output.unwrap()
};
assert!(alpha_node.is_some());
let alpha_node = alpha_node.unwrap();
let info = alpha_node.info();
if let Property::Constant(format) = info.format {
assert_eq!(format.name(), "Gray8");
} else {
assert!(false);
}
assert_eq!(
info.framerate,
Property::Constant(Framerate {
numerator: 60,
denominator: 1,
})
);
assert_eq!(
info.resolution,
Property::Constant(Resolution {
width: 1920,
height: 1080,
})
);
#[cfg(feature = "gte-vapoursynth-api-32")]
assert_eq!(info.num_frames, 100);
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
assert_eq!(info.num_frames, Property::Constant(100));
let frame = alpha_node.get_frame(0).unwrap();
let format = frame.format();
assert_eq!(format.name(), "Gray8");
assert_eq!(format.plane_count(), 1);
let resolution = frame.resolution(0);
assert_eq!(
resolution,
Resolution {
width: 1920,
height: 1080,
}
);
for row in 0..resolution.height {
let data_row = frame.data_row(0, row);
assert_eq!(&data_row[..], &[128; 1920][..]);
let data_row = frame.plane_row::<u8>(0, row);
assert_eq!(&data_row[..], &[128; 1920][..]);
}
}
fn verify_pixel_format<T: Component + Debug + Copy + PartialEq>(
env: &Environment,
index: i32,
bits_per_sample: u8,
color: [T; 3],
) {
#[cfg(feature = "gte-vsscript-api-31")]
let node = env.get_output(index).unwrap().0;
#[cfg(not(feature = "gte-vsscript-api-31"))]
let node = env.get_output(index).unwrap();
let frame = node.get_frame(0).unwrap();
let format = frame.format();
assert_eq!(format.bits_per_sample(), bits_per_sample);
let bytes_per_sample = ((bits_per_sample + 7) / 8).next_power_of_two();
assert_eq!(format.bytes_per_sample(), bytes_per_sample);
for plane_num in 0..3 {
// Compare the entire row at once for speed.
let row_gt = vec![color[plane_num]; frame.width(plane_num)];
for y in 0..frame.height(plane_num) {
let row = frame.plane_row(plane_num, y);
assert_eq!(row.len(), frame.width(plane_num));
assert_eq!(&row_gt[..], row);
}
if let Ok(data) = frame.plane(plane_num) {
assert_eq!(data.len(), frame.height(plane_num) * frame.width(plane_num));
for y in 0..frame.height(plane_num) {
assert_eq!(
&row_gt[..],
&data[y * frame.width(plane_num)..(y + 1) * frame.width(plane_num)]
);
}
}
}
}
#[test]
fn pixel_formats() {
let env = vsscript::Environment::from_file(
"test-vpy/pixel-formats.vpy",
vsscript::EvalFlags::Nothing,
).unwrap();
verify_pixel_format(&env, 0, 10, [789u16, 123u16, 456u16]);
verify_pixel_format(&env, 1, 32, [5f32, 42f32, 0.25f32]);
verify_pixel_format(&env, 2, 32, [0.125f32, 10f32, 0.5f32]);
verify_pixel_format(&env, 3, 17, [77777u32, 88888u32, 99999u32]);
verify_pixel_format(&env, 4, 32, [u32::max_value(), 12345u32, 65432u32]);
#[cfg(feature = "f16-pixel-type")]
verify_pixel_format(
&env,
5,
16,
[
half::f16::from_f32(0.0625f32),
half::f16::from_f32(5f32),
half::f16::from_f32(0.25f32),
],
);
}
#[test]
#[should_panic]
fn invalid_component_type() {
let env = vsscript::Environment::from_file(
"test-vpy/pixel-formats.vpy",
vsscript::EvalFlags::Nothing,
).unwrap();
#[cfg(feature = "gte-vsscript-api-31")]
let node = env.get_output(0).unwrap().0;
#[cfg(not(feature = "gte-vsscript-api-31"))]
let node = env.get_output(0).unwrap();
let frame = node.get_frame(0).unwrap();
let _ = frame.plane_row::<u8>(0, 0); // Should be u16.
}
#[test]
fn gradient() {
let env =
vsscript::Environment::from_file("test-vpy/gradient.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
#[cfg(feature = "gte-vsscript-api-31")]
let node = env.get_output(0).unwrap().0;
#[cfg(not(feature = "gte-vsscript-api-31"))]
let node = env.get_output(0).unwrap();
let frame = node.get_frame(0).unwrap();
for plane in 0..3 {
for row in 0..16 {
let mut gt = [0u8; 16];
for col in 0..16 {
gt[col] = match plane {
0 => row as u8 * 16,
1 => col as u8 * 16,
2 => 0,
_ => unreachable!(),
};
}
let data = frame.data_row(plane, row);
assert_eq!(data, &gt[..]);
let data = frame.plane_row::<u8>(plane, row);
assert_eq!(data, &gt[..]);
}
}
}
#[test]
fn clear_output() {
let env =
vsscript::Environment::from_script(include_str!("../test-vpy/green.vpy")).unwrap();
assert!(
env.clear_output(1)
.err()
.map(|e| if let vsscript::Error::NoOutput = e {
true
} else {
false
}).unwrap_or(false)
);
assert!(env.clear_output(0).is_ok());
assert!(
env.clear_output(0)
.err()
.map(|e| if let vsscript::Error::NoOutput = e {
true
} else {
false
}).unwrap_or(false)
);
}
#[test]
fn iterators() {
let env =
vsscript::Environment::from_script(include_str!("../test-vpy/green.vpy")).unwrap();
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = env.get_output(0).unwrap();
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (env.get_output(0).unwrap(), None::<Node>);
assert!(alpha_node.is_none());
let frame = node.get_frame(0).unwrap();
let props = frame.props();
assert_eq!(props.keys().size_hint(), (2, Some(2)));
// assert_eq!(props.iter().size_hint(), (2, Some(2)));
}
#[test]
fn vsscript_variables() {
let env =
vsscript::Environment::from_script(include_str!("../test-vpy/green.vpy")).unwrap();
let mut map = OwnedMap::new(API::get().unwrap());
assert!(env.get_variable("video", &mut map).is_ok());
assert!(env.clear_variable("video").is_ok());
assert!(env.clear_variable("video").is_err());
assert!(env.get_variable("video", &mut map).is_err());
assert!(env.set_variables(&map).is_ok());
assert!(env.get_variable("video", &mut map).is_ok());
}
#[test]
fn get_frame_async() {
let env =
vsscript::Environment::from_file("test-vpy/green.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = {
let output = env.get_output(0);
assert!(output.is_ok());
output.unwrap()
};
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (env.get_output(0).unwrap(), None::<Node>);
assert!(alpha_node.is_none());
let mut rxs = Vec::new();
for i in 0..10 {
let (tx, rx) = channel();
rxs.push(rx);
node.get_frame_async(i, move |frame, n, node| {
assert!(frame.is_ok());
let frame = frame.unwrap();
assert_eq!(n, i);
assert_eq!(
node.info().framerate,
Property::Constant(Framerate {
numerator: 60,
denominator: 1,
})
);
green_frame_test(&frame);
props_test(&frame, 60);
assert_eq!(tx.send(()), Ok(()));
});
}
drop(node); // Test dropping prematurely.
for rx in rxs {
assert_eq!(rx.recv(), Ok(()));
}
}
#[test]
fn get_frame_async_error() {
let env =
vsscript::Environment::from_file("test-vpy/green.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
#[cfg(feature = "gte-vsscript-api-31")]
let (node, alpha_node) = {
let output = env.get_output(0);
assert!(output.is_ok());
output.unwrap()
};
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, alpha_node) = (env.get_output(0).unwrap(), None::<Node>);
assert!(alpha_node.is_none());
let (tx, rx) = channel();
// The clip only has 100 frames, so requesting the 101th one produces an error.
node.get_frame_async(100, move |frame, n, node| {
assert!(frame.is_err());
assert_eq!(n, 100);
assert_eq!(
node.info().framerate,
Property::Constant(Framerate {
numerator: 60,
denominator: 1,
})
);
assert_eq!(tx.send(()), Ok(()));
});
assert_eq!(rx.recv(), Ok(()));
}
#[test]
fn core() {
let env =
vsscript::Environment::from_file("test-vpy/green.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
let core = env.get_core();
assert!(core.is_ok());
let core = core.unwrap();
let yuv420p8 = core.get_format(PresetFormat::YUV420P8.into());
assert!(yuv420p8.is_some());
let yuv420p8 = yuv420p8.unwrap();
assert_eq!(yuv420p8.id(), PresetFormat::YUV420P8.into());
assert_eq!(yuv420p8.name(), "YUV420P8");
assert_eq!(yuv420p8.plane_count(), 3);
assert_eq!(yuv420p8.color_family(), ColorFamily::YUV);
assert_eq!(yuv420p8.sample_type(), SampleType::Integer);
assert_eq!(yuv420p8.bits_per_sample(), 8);
assert_eq!(yuv420p8.bytes_per_sample(), 1);
assert_eq!(yuv420p8.sub_sampling_w(), 1);
assert_eq!(yuv420p8.sub_sampling_h(), 1);
let yuv422p8 = core.get_format(PresetFormat::YUV422P8.into()).unwrap();
assert_eq!(yuv422p8.sub_sampling_w(), 1);
assert_eq!(yuv422p8.sub_sampling_h(), 0);
}
#[test]
fn plugins() {
let env =
vsscript::Environment::from_file("test-vpy/green.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
fn bind<'a, T: ?Sized, U: ?Sized>(_: &'a T, x: &'a U) -> &'a U {
x
}
let core = env.get_core().unwrap();
let plugins = core.plugins();
let ids: Vec<_> = plugins
.keys()
.filter_map(|key| unsafe {
bind(
key,
CStr::from_ptr(plugins.get_data(key).unwrap().as_ptr() as _),
).to_str()
.ok()
}).filter_map(|id| id.split(';').nth(1))
.collect();
assert!(ids.contains(&"com.vapoursynth.std"));
assert!(ids.contains(&"com.vapoursynth.resize"));
let std = core.get_plugin_by_id("com.vapoursynth.std");
assert!(std.is_ok());
let std = std.unwrap();
assert!(std.is_some());
let std = std.unwrap();
let functions = std.functions();
let names: Vec<_> = functions
.keys()
.filter_map(|key| unsafe {
bind(
key,
CStr::from_ptr(functions.get_data(key).unwrap().as_ptr() as _),
).to_str()
.ok()
.map(|value| (key, value))
}).filter_map(|(key, value)| value.split(';').nth(0).map(|name| (key, name)))
.collect();
assert!(names.contains(&("CropRel", "CropRel")));
#[cfg(feature = "gte-vsscript-api-31")]
let (node, _) = env.get_output(0).unwrap();
#[cfg(not(feature = "gte-vsscript-api-31"))]
let (node, _) = (env.get_output(0).unwrap(), None::<Node>);
let mut args = OwnedMap::new(API::get().unwrap());
args.set_node("clip", &node);
args.set_int("left", 100);
let rv = std.invoke("CropRel", &args).unwrap();
assert!(rv.error().is_none());
let node = rv.get_node("clip");
assert!(node.is_ok());
let node = node.unwrap();
let frame = node.get_frame(0).unwrap();
assert_eq!(
frame.resolution(0),
Resolution {
width: 1820,
height: 1080,
}
);
}
#[test]
fn functions() {
let env =
vsscript::Environment::from_file("test-vpy/green.vpy", vsscript::EvalFlags::Nothing)
.unwrap();
let core = env.get_core().unwrap();
let api = API::get().unwrap();
let function = Function::new(api, core, |_api, _core, in_, out| {
assert_eq!(in_.get_int("hello").unwrap(), 1337);
out.set_int("there", 42).unwrap();
});
let mut in_ = OwnedMap::new(api);
let mut out = OwnedMap::new(api);
in_.set_int("hello", 1337).unwrap();
function.call(&in_, &mut out);
assert!(out.error().is_none());
assert_eq!(out.get_int("there").unwrap(), 42);
}
}
// We need either VSScript API 3.2 or the VapourSynth functions.
#[cfg(any(
feature = "vapoursynth-functions",
all(
feature = "vsscript-functions",
feature = "gte-vsscript-api-32"
)
))]
mod need_api {
use std::ffi::CString;
use std::sync::mpsc::{channel, Sender};
use std::sync::Mutex;
use super::*;
use prelude::*;
#[test]
fn maps() {
let mut map = OwnedMap::new(API::get().unwrap());
assert_eq!(map.key_count(), 0);
assert_eq!(map.touch("test_frame", ValueType::Frame), Ok(()));
assert_eq!(map.value_type("test_frame"), Ok(ValueType::Frame));
assert_eq!(map.value_count("test_frame"), Ok(0));
assert_eq!(
map.append_int("test_frame", 42),
Err(map::Error::WrongValueType)
);
assert_eq!(
map.append("test_frame", &42),
Err(map::Error::WrongValueType)
);
assert_eq!(map.set_int("i", 42), Ok(()));
assert_eq!(map.get_int("i"), Ok(42));
assert_eq!(map.append_int("i", 43), Ok(()));
assert_eq!(map.get_int("i"), Ok(42));
{
let iter = map.get_int_iter("i");
assert!(iter.is_ok());
let mut iter = iter.unwrap();
assert_eq!(iter.next(), Some(42));
assert_eq!(iter.next(), Some(43));
assert_eq!(iter.next(), None);
}
assert_eq!(map.set("i", &42), Ok(()));
assert_eq!(map.get("i"), Ok(42));
assert_eq!(map.append("i", &43), Ok(()));
assert_eq!(map.get("i"), Ok(42));
{
let iter = map.get_iter::<i64>("i");
assert!(iter.is_ok());
let mut iter = iter.unwrap();
assert_eq!(iter.next(), Some(42));
assert_eq!(iter.next(), Some(43));
assert_eq!(iter.next(), None);
}
#[cfg(feature = "gte-vapoursynth-api-31")]
{
assert_eq!(map.get_int_array("i"), Ok(&[42, 43][..]));
assert_eq!(map.set_int_array("ia", &[10, 20, 30]), Ok(()));
assert_eq!(map.get_int_array("ia"), Ok(&[10, 20, 30][..]));
}
assert_eq!(map.set_float("f", 42f64), Ok(()));
assert_eq!(map.get_float("f"), Ok(42f64));
assert_eq!(map.append_float("f", 43f64), Ok(()));
assert_eq!(map.get_float("f"), Ok(42f64));
{
let iter = map.get_float_iter("f");
assert!(iter.is_ok());
let mut iter = iter.unwrap();
assert_eq!(iter.next(), Some(42f64));
assert_eq!(iter.next(), Some(43f64));
assert_eq!(iter.next(), None);
}
assert_eq!(map.set("f", &42f64), Ok(()));
assert_eq!(map.get("f"), Ok(42f64));
assert_eq!(map.append("f", &43f64), Ok(()));
assert_eq!(map.get("f"), Ok(42f64));
{
let iter = map.get_iter::<f64>("f");
assert!(iter.is_ok());
let mut iter = iter.unwrap();
assert_eq!(iter.next(), Some(42f64));
assert_eq!(iter.next(), Some(43f64));
assert_eq!(iter.next(), None);
}
#[cfg(feature = "gte-vapoursynth-api-31")]
{
assert_eq!(map.get_float_array("f"), Ok(&[42f64, 43f64][..]));
assert_eq!(map.set_float_array("fa", &[10f64, 20f64, 30f64]), Ok(()));
assert_eq!(map.get_float_array("fa"), Ok(&[10f64, 20f64, 30f64][..]));
}
assert_eq!(map.set_data("d", &[1, 2, 3]), Ok(()));
assert_eq!(map.get_data("d"), Ok(&[1, 2, 3][..]));
assert_eq!(map.append_data("d", &[4, 5, 6]), Ok(()));
assert_eq!(map.get_data("d"), Ok(&[1, 2, 3][..]));
{
let iter = map.get_data_iter("d");
assert!(iter.is_ok());
let mut iter = iter.unwrap();
assert_eq!(iter.next(), Some(&[1, 2, 3][..]));
assert_eq!(iter.next(), Some(&[4, 5, 6][..]));
assert_eq!(iter.next(), None);
}
assert_eq!(map.set("d", &&[1, 2, 3][..]), Ok(()));
assert_eq!(map.get("d"), Ok(&[1, 2, 3][..]));
assert_eq!(map.append("d", &&[4, 5, 6][..]), Ok(()));
assert_eq!(map.get("d"), Ok(&[1, 2, 3][..]));
{
let iter = map.get_iter::<&[u8]>("d");
assert!(iter.is_ok());
let mut iter = iter.unwrap();
assert_eq!(iter.next(), Some(&[1, 2, 3][..]));
assert_eq!(iter.next(), Some(&[4, 5, 6][..]));
assert_eq!(iter.next(), None);
}
// TODO: node, frame and function method tests when we can make them.
assert_eq!(map.delete_key("test_frame"), Ok(()));
assert_eq!(map.delete_key("test_frame"), Err(map::Error::KeyNotFound));
assert_eq!(map.error(), None);
assert_eq!(map.set_error("hello there"), Ok(()));
assert_eq!(
map.error().as_ref().map(|x| x.as_ref()),
Some("hello there")
);
}
#[cfg(feature = "gte-vapoursynth-api-34")]
#[test]
fn message_handler() {
let api = API::get().unwrap();
let (tx, rx) = channel();
// Hopefully no one logs anything here and breaks the test.
api.set_message_handler(move |message_type, message| {
assert_eq!(tx.send((message_type, message.to_owned())), Ok(()));
});
assert_eq!(
api.log(MessageType::Warning, "test warning message"),
Ok(())
);
assert_eq!(
rx.recv(),
Ok((
MessageType::Warning,
CString::new("test warning message").unwrap()
))
);
assert_eq!(api.log(MessageType::Debug, "test debug message"), Ok(()));
assert_eq!(
rx.recv(),
Ok((
MessageType::Debug,
CString::new("test debug message").unwrap()
))
);
assert_eq!(
api.log(MessageType::Critical, "test critical message"),
Ok(())
);
assert_eq!(
rx.recv(),
Ok((
MessageType::Critical,
CString::new("test critical message").unwrap()
))
);
{
lazy_static! {
static ref SENDER: Mutex<Option<Sender<(MessageType, CString)>>> = Mutex::new(None);
}
let (tx, rx) = channel();
*SENDER.lock().unwrap() = Some(tx);
api.set_message_handler_trivial(|message_type, message| {
let guard = SENDER.lock().unwrap();
let tx = guard.as_ref().unwrap();
assert_eq!(tx.send((message_type, message.to_owned())), Ok(()));
});
assert_eq!(
api.log(MessageType::Warning, "test warning message"),
Ok(())
);
assert_eq!(
rx.recv(),
Ok((
MessageType::Warning,
CString::new("test warning message").unwrap()
))
);
assert_eq!(api.log(MessageType::Debug, "test debug message"), Ok(()));
assert_eq!(
rx.recv(),
Ok((
MessageType::Debug,
CString::new("test debug message").unwrap()
))
);
assert_eq!(
api.log(MessageType::Critical, "test critical message"),
Ok(())
);
assert_eq!(
rx.recv(),
Ok((
MessageType::Critical,
CString::new("test critical message").unwrap()
))
);
}
api.clear_message_handler();
}
}

View File

@ -0,0 +1,183 @@
//! Video clip formats.
use std::fmt::Debug;
use std::ops::Deref;
use std::ptr;
use vapoursynth_sys as ffi;
use format::Format;
use node;
/// Represents video resolution.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Resolution {
/// Width of the clip, greater than 0.
pub width: usize,
/// Height of the clip, greater than 0.
pub height: usize,
}
/// Represents video framerate.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Framerate {
/// FPS numerator, greater than 0.
pub numerator: u64,
/// FPS denominator, greater than 0.
pub denominator: u64,
}
/// Represents a property that can be either constant or variable, like the resolution or the
/// framerate.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Property<T: Debug + Clone + Copy + Eq + PartialEq> {
/// This property is variable.
Variable,
/// This property is constant.
Constant(T),
}
/// Contains information about a video clip.
#[derive(Debug, Copy, Clone)]
pub struct VideoInfo<'core> {
/// Format of the clip.
pub format: Property<Format<'core>>,
/// Framerate of the clip.
pub framerate: Property<Framerate>,
/// Resolution of the clip.
pub resolution: Property<Resolution>,
/// Length of the clip, greater than 0.
#[cfg(feature = "gte-vapoursynth-api-32")]
pub num_frames: usize,
/// Length of the clip.
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
pub num_frames: Property<usize>,
/// The flags of this clip.
pub flags: node::Flags,
}
impl<'core> VideoInfo<'core> {
/// Creates a `VideoInfo` from a raw pointer.
///
/// # Safety
/// The caller must ensure `ptr` and the lifetime is valid.
pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSVideoInfo) -> Self {
let info = &*ptr;
debug_assert!(info.fpsNum >= 0);
debug_assert!(info.fpsDen >= 0);
debug_assert!(info.width >= 0);
debug_assert!(info.height >= 0);
debug_assert!(info.numFrames >= 0);
let format = if info.format.is_null() {
Property::Variable
} else {
Property::Constant(Format::from_ptr(info.format))
};
let framerate = if info.fpsNum == 0 {
debug_assert!(info.fpsDen == 0);
Property::Variable
} else {
debug_assert!(info.fpsDen != 0);
Property::Constant(Framerate {
numerator: info.fpsNum as _,
denominator: info.fpsDen as _,
})
};
let resolution = if info.width == 0 {
debug_assert!(info.height == 0);
Property::Variable
} else {
debug_assert!(info.height != 0);
Property::Constant(Resolution {
width: info.width as _,
height: info.height as _,
})
};
#[cfg(feature = "gte-vapoursynth-api-32")]
let num_frames = {
debug_assert!(info.numFrames != 0);
info.numFrames as _
};
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
let num_frames = {
if info.numFrames == 0 {
Property::Variable
} else {
Property::Constant(info.numFrames as _)
}
};
Self {
format,
framerate,
resolution,
num_frames,
flags: ffi::VSNodeFlags(info.flags).into(),
}
}
/// Converts the Rust struct into a C struct.
pub(crate) fn ffi_type(self) -> ffi::VSVideoInfo {
let format = match self.format {
Property::Variable => ptr::null(),
Property::Constant(x) => x.deref(),
};
let (fps_num, fps_den) = match self.framerate {
Property::Variable => (0, 0),
Property::Constant(Framerate {
numerator,
denominator,
}) => (numerator as i64, denominator as i64),
};
let (width, height) = match self.resolution {
Property::Variable => (0, 0),
Property::Constant(Resolution { width, height }) => (width as i32, height as i32),
};
#[cfg(feature = "gte-vapoursynth-api-32")]
let num_frames = self.num_frames as i32;
#[cfg(not(feature = "gte-vapoursynth-api-32"))]
let num_frames = match self.num_frames {
Property::Variable => 0,
Property::Constant(x) => x as i32,
};
let flags = self.flags.bits();
ffi::VSVideoInfo {
format,
fpsNum: fps_num,
fpsDen: fps_den,
width,
height,
numFrames: num_frames,
flags,
}
}
}
impl<T> From<T> for Property<T>
where
T: Debug + Clone + Copy + Eq + PartialEq,
{
#[inline]
fn from(x: T) -> Self {
Property::Constant(x)
}
}

View File

@ -0,0 +1,278 @@
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::ptr;
use std::ptr::NonNull;
use vapoursynth_sys as ffi;
use api::API;
use core::CoreRef;
use map::Map;
use node::Node;
use vsscript::errors::Result;
use vsscript::*;
/// VSScript file evaluation flags.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum EvalFlags {
Nothing,
/// The working directory will be changed to the script's directory for the evaluation.
SetWorkingDir,
}
impl EvalFlags {
#[inline]
fn ffi_type(self) -> ::std::os::raw::c_int {
match self {
EvalFlags::Nothing => 0,
EvalFlags::SetWorkingDir => ffi::VSEvalFlags::efSetWorkingDir as _,
}
}
}
/// Contains two possible variants of arguments to `Environment::evaluate_script()`.
#[derive(Clone, Copy)]
enum EvaluateScriptArgs<'a> {
/// Evaluate a script contained in the string.
Script(&'a str),
/// Evaluate a script contained in the file.
File(&'a Path, EvalFlags),
}
/// A wrapper for the VSScript environment.
#[derive(Debug)]
pub struct Environment {
handle: NonNull<ffi::VSScript>,
}
unsafe impl Send for Environment {}
unsafe impl Sync for Environment {}
impl Drop for Environment {
#[inline]
fn drop(&mut self) {
unsafe {
ffi::vsscript_freeScript(self.handle.as_ptr());
}
}
}
impl Environment {
/// Retrieves the VSScript error message.
///
/// # Safety
/// This function must only be called if an error is present.
#[inline]
unsafe fn error(&self) -> CString {
let message = ffi::vsscript_getError(self.handle.as_ptr());
CStr::from_ptr(message).to_owned()
}
/// Creates an empty script environment.
///
/// Useful if it is necessary to set some variable in the script environment before evaluating
/// any scripts.
pub fn new() -> Result<Self> {
maybe_initialize();
let mut handle = ptr::null_mut();
let rv = unsafe { call_vsscript!(ffi::vsscript_createScript(&mut handle)) };
let environment = Self {
handle: unsafe { NonNull::new_unchecked(handle) },
};
if rv != 0 {
Err(VSScriptError::new(unsafe { environment.error() }).into())
} else {
Ok(environment)
}
}
/// Calls `vsscript_evaluateScript()`.
///
/// `self` is taken by a mutable reference mainly to ensure the atomicity of a call to
/// `vsscript_evaluateScript()` (a function that could produce an error) and the following call
/// to `vsscript_getError()`. If atomicity is not enforced, another thread could perform some
/// operation between these two and clear or change the error message.
fn evaluate_script(&mut self, args: EvaluateScriptArgs) -> Result<()> {
let (script, path, flags) = match args {
EvaluateScriptArgs::Script(script) => (script.to_owned(), None, EvalFlags::Nothing),
EvaluateScriptArgs::File(path, flags) => {
let mut file = File::open(path).map_err(Error::FileOpen)?;
let mut script = String::new();
file.read_to_string(&mut script).map_err(Error::FileRead)?;
// vsscript throws an error if it's not valid UTF-8 anyway.
let path = path.to_str().ok_or(Error::PathInvalidUnicode)?;
let path = CString::new(path)?;
(script, Some(path), flags)
}
};
let script = CString::new(script)?;
let rv = unsafe {
call_vsscript!(ffi::vsscript_evaluateScript(
&mut self.handle.as_ptr(),
script.as_ptr(),
path.as_ref().map(|p| p.as_ptr()).unwrap_or(ptr::null()),
flags.ffi_type(),
))
};
if rv != 0 {
Err(VSScriptError::new(unsafe { self.error() }).into())
} else {
Ok(())
}
}
/// Creates a script environment and evaluates a script contained in a string.
#[inline]
pub fn from_script(script: &str) -> Result<Self> {
let mut environment = Self::new()?;
environment.evaluate_script(EvaluateScriptArgs::Script(script))?;
Ok(environment)
}
/// Creates a script environment and evaluates a script contained in a file.
#[inline]
pub fn from_file<P: AsRef<Path>>(path: P, flags: EvalFlags) -> Result<Self> {
let mut environment = Self::new()?;
environment.evaluate_script(EvaluateScriptArgs::File(path.as_ref(), flags))?;
Ok(environment)
}
/// Evaluates a script contained in a string.
#[inline]
pub fn eval_script(&mut self, script: &str) -> Result<()> {
self.evaluate_script(EvaluateScriptArgs::Script(script))
}
/// Evaluates a script contained in a file.
#[inline]
pub fn eval_file<P: AsRef<Path>>(&mut self, path: P, flags: EvalFlags) -> Result<()> {
self.evaluate_script(EvaluateScriptArgs::File(path.as_ref(), flags))
}
/// Clears the script environment.
#[inline]
pub fn clear(&self) {
unsafe {
ffi::vsscript_clearEnvironment(self.handle.as_ptr());
}
}
/// Retrieves a node from the script environment. A node in the script must have been marked
/// for output with the requested index.
#[cfg(all(
not(feature = "gte-vsscript-api-31"),
feature = "vapoursynth-functions"
))]
#[inline]
pub fn get_output(&self, index: i32) -> Result<Node> {
// Node needs the API.
API::get().ok_or(Error::NoAPI)?;
let node_handle = unsafe { ffi::vsscript_getOutput(self.handle.as_ptr(), index) };
if node_handle.is_null() {
Err(Error::NoOutput)
} else {
Ok(unsafe { Node::from_ptr(node_handle) })
}
}
/// Retrieves a node from the script environment. A node in the script must have been marked
/// for output with the requested index. The second node, if any, contains the alpha clip.
#[cfg(all(
feature = "gte-vsscript-api-31",
any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
)
))]
#[inline]
pub fn get_output(&self, index: i32) -> Result<(Node, Option<Node>)> {
// Node needs the API.
API::get().ok_or(Error::NoAPI)?;
let mut alpha_handle = ptr::null_mut();
let node_handle =
unsafe { ffi::vsscript_getOutput2(self.handle.as_ptr(), index, &mut alpha_handle) };
if node_handle.is_null() {
return Err(Error::NoOutput);
}
let node = unsafe { Node::from_ptr(node_handle) };
let alpha_node = unsafe { alpha_handle.as_mut().map(|p| Node::from_ptr(p)) };
Ok((node, alpha_node))
}
/// Cancels a node set for output. The node will no longer be available to `get_output()`.
#[inline]
pub fn clear_output(&self, index: i32) -> Result<()> {
let rv = unsafe { ffi::vsscript_clearOutput(self.handle.as_ptr(), index) };
if rv != 0 {
Err(Error::NoOutput)
} else {
Ok(())
}
}
/// Retrieves the VapourSynth core that was created in the script environment. If a VapourSynth
/// core has not been created yet, it will be created now, with the default options.
#[cfg(any(
feature = "vapoursynth-functions",
feature = "gte-vsscript-api-32"
))]
pub fn get_core(&self) -> Result<CoreRef> {
// CoreRef needs the API.
API::get().ok_or(Error::NoAPI)?;
let ptr = unsafe { ffi::vsscript_getCore(self.handle.as_ptr()) };
if ptr.is_null() {
Err(Error::NoCore)
} else {
Ok(unsafe { CoreRef::from_ptr(ptr) })
}
}
/// Retrieves a variable from the script environment.
pub fn get_variable(&self, name: &str, map: &mut Map) -> Result<()> {
let name = CString::new(name)?;
let rv = unsafe {
ffi::vsscript_getVariable(self.handle.as_ptr(), name.as_ptr(), map.deref_mut())
};
if rv != 0 {
Err(Error::NoSuchVariable)
} else {
Ok(())
}
}
/// Sets variables in the script environment.
pub fn set_variables(&self, variables: &Map) -> Result<()> {
let rv = unsafe { ffi::vsscript_setVariable(self.handle.as_ptr(), variables.deref()) };
if rv != 0 {
Err(Error::NoSuchVariable)
} else {
Ok(())
}
}
/// Deletes a variable from the script environment.
pub fn clear_variable(&self, name: &str) -> Result<()> {
let name = CString::new(name)?;
let rv = unsafe { ffi::vsscript_clearVariable(self.handle.as_ptr(), name.as_ptr()) };
if rv != 0 {
Err(Error::NoSuchVariable)
} else {
Ok(())
}
}
}

View File

@ -0,0 +1,60 @@
use std::ffi::{CString, NulError};
use std::{fmt, io, result};
/// The error type for `vsscript` operations.
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "Couldn't convert to a CString")]
CStringConversion(#[cause] NulError),
#[fail(display = "Couldn't open the file")]
FileOpen(#[cause] io::Error),
#[fail(display = "Couldn't read the file")]
FileRead(#[cause] io::Error),
#[fail(display = "Path isn't valid Unicode")]
PathInvalidUnicode,
#[fail(display = "An error occurred in VSScript")]
VSScript(#[cause] VSScriptError),
#[fail(display = "There's no such variable")]
NoSuchVariable,
#[fail(display = "Couldn't get the core")]
NoCore,
#[fail(display = "There's no output on the requested index")]
NoOutput,
#[fail(display = "Couldn't get the VapourSynth API")]
NoAPI,
}
impl From<NulError> for Error {
#[inline]
fn from(x: NulError) -> Self {
Error::CStringConversion(x)
}
}
impl From<VSScriptError> for Error {
#[inline]
fn from(x: VSScriptError) -> Self {
Error::VSScript(x)
}
}
pub(crate) type Result<T> = result::Result<T, Error>;
/// A container for a VSScript error.
#[derive(Fail, Debug)]
pub struct VSScriptError(CString);
impl fmt::Display for VSScriptError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.to_string_lossy())
}
}
impl VSScriptError {
/// Creates a new `VSScriptError` with the given error message.
#[inline]
pub(crate) fn new(message: CString) -> Self {
VSScriptError(message)
}
}

View File

@ -0,0 +1,69 @@
//! VapourSynth script-related things.
#[cfg(not(feature = "gte-vsscript-api-32"))]
use std::sync::Mutex;
use std::sync::{Once, ONCE_INIT};
use vapoursynth_sys as ffi;
#[cfg(not(feature = "gte-vsscript-api-32"))]
lazy_static! {
static ref FFI_CALL_MUTEX: Mutex<()> = Mutex::new(());
}
// Some `vsscript_*` function calls have threading issues. Protect them with a mutex.
// https://github.com/vapoursynth/vapoursynth/issues/367
macro_rules! call_vsscript {
($call:expr) => {{
// Fixed in VSScript API 3.2.
// TODO: also not needed when we're running API 3.2 even without a feature.
#[cfg(not(feature = "gte-vsscript-api-32"))]
let _lock = FFI_CALL_MUTEX.lock();
$call
}};
}
/// Ensures `vsscript_init()` has been called at least once.
// TODO: `vsscript_init()` is already thread-safe with `std::call_once()`, maybe this can be done
// differently to remove the thread protection on Rust's side? An idea is to have a special type
// which calls `vsscript_init()` in `new()` and `vsscript_finalize()` in `drop()` and have the rest
// of the API accessible through that type, however that could become somewhat unergonomic with
// having to store its lifetime everywhere and potentially pass it around the threads.
#[inline]
pub(crate) fn maybe_initialize() {
static ONCE: Once = ONCE_INIT;
ONCE.call_once(|| unsafe {
ffi::vsscript_init();
// Verify the VSScript API version.
#[cfg(feature = "gte-vsscript-api-31")]
{
fn split_version(version: i32) -> (i32, i32) {
(version >> 16, version & 0xFFFF)
}
let vsscript_version = ffi::vsscript_getApiVersion();
let (major, minor) = split_version(vsscript_version);
let (my_major, my_minor) = split_version(ffi::VSSCRIPT_API_VERSION);
if my_major != major {
panic!(
"Invalid VSScript major API version (expected: {}, got: {})",
my_major, major
);
} else if my_minor > minor {
panic!(
"Invalid VSScript minor API version (expected: >= {}, got: {})",
my_minor, minor
);
}
}
});
}
mod errors;
pub use self::errors::{Error, VSScriptError};
mod environment;
pub use self::environment::{Environment, EvalFlags};

View File

@ -0,0 +1,17 @@
import vapoursynth as vs
from vapoursynth import core
video = core.std.BlankClip(width = 1920,
height = 1080,
format = vs.RGB24,
length = 100,
fpsnum = 60,
fpsden = 1,
color = [0, 255, 0])
alpha = core.std.BlankClip(width = 1920,
height = 1080,
format = vs.GRAY8,
length = 100,
fpsnum = 60,
fpsden = 1,
color = [128])
video.set_output(alpha = alpha)

View File

@ -0,0 +1,17 @@
import vapoursynth as vs
from vapoursynth import core
def pixel(color):
return core.std.BlankClip(width = 1,
height = 1,
format = vs.RGB24,
length = 1,
color = color)
def row(red):
return core.std.StackHorizontal(
[pixel([red, green * 16, 0]) for green in range(16)])
clip = core.std.StackVertical([row(red * 16) for red in range(16)])
# clip = core.resize.Lanczos(clip, format = vs.YUV444P8, matrix_s = '709')
clip.set_output()

View File

@ -0,0 +1,11 @@
import vapoursynth as vs
from vapoursynth import core
video = core.std.BlankClip(width = 1920,
height = 1080,
format = vs.RGB24,
length = 100,
fpsnum = 60,
fpsden = 1,
color = [0, 255, 0])
# video = core.resize.Bicubic(video, format = vs.YUV444P8, matrix_s = '709')
video.set_output()

View File

@ -0,0 +1,20 @@
import vapoursynth as vs
from vapoursynth import core
def make_clip(format, color):
return core.std.BlankClip(width = 320,
height = 240,
format = format,
length = 100,
fpsnum = 60,
fpsden = 1,
color = color)
make_clip(vs.YUV420P10, [789, 123, 456]).set_output(0)
make_clip(vs.YUV444PS, [5.0, 42.0, 0.25]).set_output(1)
make_clip(vs.RGBS, [0.125, 10.0, 0.5]).set_output(2)
format = core.register_format(vs.YUV, vs.INTEGER, 17, 0, 0)
make_clip(format.id, [77777, 88888, 99999]).set_output(3)
format = core.register_format(vs.YUV, vs.INTEGER, 32, 0, 0)
make_clip(format.id, [2**32-1, 12345, 65432]).set_output(4)
make_clip(vs.RGBH, [0.0625, 5.0, 0.25]).set_output(5)

View File

@ -0,0 +1,18 @@
import vapoursynth as vs
from vapoursynth import core
video = core.std.BlankClip(width = 1920,
height = 1080,
format = vs.RGB24,
length = 100,
fpsnum = 60,
fpsden = 1,
color = [0, 255, 0])
video2 = core.std.BlankClip(width = 1280,
height = 720,
format = vs.GRAY8,
length = 100,
fpsnum = 30,
fpsden = 1,
color = [127])
# video = core.resize.Bicubic(video, format = vs.YUV444P8, matrix_s = '709')
core.std.Splice([video, video2], mismatch = True).set_output()